< Summary

Class:Fusion.Editor.ReadOnlyAttributeDrawer
Assembly:Fusion.Unity.Editor
File(s):C:/Users/LOQ/ASM/Assets/Photon/Fusion/Editor/Fusion.Unity.Editor.cs
Covered lines:0
Uncovered lines:7
Coverable lines:7
Total lines:14485
Line coverage:0% (0 of 7)
Covered branches:0
Total branches:0
Covered methods:0
Total methods:1
Method coverage:0% (0 of 1)

Coverage History

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
OnGUIInternal(...)0%0000%

File(s)

C:/Users/LOQ/ASM/Assets/Photon/Fusion/Editor/Fusion.Unity.Editor.cs

#LineLine coverage
 1#if !FUSION_DEV
 2
 3#region Assets/Photon/Fusion/Editor/AssetObjectEditor.cs
 4
 5namespace Fusion.Editor {
 6  using UnityEditor;
 7
 8  [CustomEditor(typeof(AssetObject), true)]
 9  public class AssetObjectEditor : UnityEditor.Editor {
 10    public override void OnInspectorGUI() {
 11      base.OnInspectorGUI();
 12    }
 13  }
 14}
 15
 16
 17#endregion
 18
 19
 20#region Assets/Photon/Fusion/Editor/BehaviourEditor.cs
 21
 22namespace Fusion.Editor {
 23
 24  using System;
 25  using System.Collections.Generic;
 26  using System.Linq;
 27  using UnityEditor;
 28
 29  [CustomEditor(typeof(Fusion.Behaviour), true)]
 30  [CanEditMultipleObjects]
 31  public partial class BehaviourEditor : FusionEditor {
 32  }
 33}
 34
 35
 36#endregion
 37
 38
 39#region Assets/Photon/Fusion/Editor/ChangeDllManager.cs
 40
 41namespace Fusion.Editor {
 42  using System;
 43  using System.IO;
 44  using System.Linq;
 45  using UnityEditor;
 46  using UnityEngine;
 47
 48  /// <summary>
 49  /// Provides methods to toggle between different DLL modes for the Fusion framework.
 50  /// </summary>
 51  public static class ChangeDllManager {
 52    private const string FusionRuntimeDllGuid = "e725a070cec140c4caffb81624c8c787";
 53
 54    private static readonly string[] FileList = { "Fusion.Common.dll", "Fusion.Runtime.dll", "Fusion.Realtime.dll", "Fus
 55
 56    /// <summary>
 57    /// Changes the DLL mode to Debug.
 58    /// </summary>
 59    [MenuItem("Tools/Fusion/Change Dll Mode/Debug", false, 500)]
 60    public static void ChangeDllModeToSharedDebug() {
 61      ChangeDllMode(NetworkRunner.BuildTypes.Debug);
 62    }
 63
 64    /// <summary>
 65    /// Changes the DLL mode to Release.
 66    /// </summary>
 67    [MenuItem("Tools/Fusion/Change Dll Mode/Release", false, 501)]
 68    public static void ChangeDllModeToSharedRelease() {
 69      ChangeDllMode(NetworkRunner.BuildTypes.Release);
 70    }
 71
 72    /// <summary>
 73    /// Changes the DLL mode based on the specified build type and build mode.
 74    /// </summary>
 75    /// <param name="buildType">The build type (<see cref="NetworkRunner.BuildTypes"/>).</param>
 76    private static void ChangeDllMode(NetworkRunner.BuildTypes buildType) {
 77      if (NetworkRunner.BuildType == buildType) {
 78        Debug.Log($"Fusion Dll Mode is already {buildType}");
 79        return;
 80      }
 81
 82      Debug.Log($"Changing Fusion Dll Mode from {NetworkRunner.BuildType} to {buildType}");
 83
 84      var targetExtension = $"{GetBuildTypeExtension(buildType)}";
 85      var targetSubFolder = GetBuildTypeSubFolder(buildType);
 86
 87      // find the root
 88      var fusionRuntimeDllPath = AssetDatabase.GUIDToAssetPath(FusionRuntimeDllGuid);
 89      if (string.IsNullOrEmpty(fusionRuntimeDllPath)) {
 90        Debug.LogError($"Cannot locate Fusion assemblies directory");
 91        return;
 92      }
 93
 94      // Check if all dlls are present
 95      var assembliesDir        = PathUtils.Normalize(Path.GetDirectoryName(fusionRuntimeDllPath));
 96      var originalFileTemplate = $"{assembliesDir}/{{0}}";
 97      var targetFileTemplate   = $"{assembliesDir}/{targetSubFolder}/{{0}}{targetExtension}";
 98      var currentDlls          = FileList.All(f => File.Exists(string.Format(originalFileTemplate, f)));
 99      var targetDlls           = FileList.All(f => File.Exists(string.Format(targetFileTemplate, f)));
 100
 101      if (currentDlls == false) {
 102        Debug.LogError("Cannot find all Fusion dlls");
 103        return;
 104      }
 105
 106      if (targetDlls == false) {
 107        Debug.LogError($"Cannot find all Fusion dlls marked with {targetExtension}");
 108        return;
 109      }
 110
 111      if (FileList.Any(f => new FileInfo(string.Format(targetFileTemplate, f)).Length == 0)) {
 112        Debug.LogError("Targets dlls are not valid");
 113        return;
 114      }
 115
 116      // Move the files
 117      try {
 118        foreach (var f in FileList) {
 119          var source = string.Format(targetFileTemplate, f);
 120          var dest   = string.Format(originalFileTemplate, f);
 121
 122          Debug.Log($"Moving {source} to {dest}");
 123          FileUtil.ReplaceFile(source, dest);
 124        }
 125
 126        Debug.Log($"Activated Fusion {buildType} dlls");
 127      } catch (Exception e) {
 128        Debug.LogAssertion(e);
 129        Debug.LogError($"Failed to Change Fusion Dll Mode");
 130      }
 131
 132      AssetDatabase.Refresh();
 133
 134      return;
 135
 136      // Gets the file extension for the specified build type.
 137      string GetBuildTypeExtension(NetworkRunner.BuildTypes referenceBuildType) =>
 138        referenceBuildType switch {
 139          NetworkRunner.BuildTypes.Debug   => ".debug",
 140          NetworkRunner.BuildTypes.Release => ".release",
 141          _                                => throw new ArgumentOutOfRangeException()
 142        };
 143
 144      // Gets the subfolder name for the specified build type.
 145      string GetBuildTypeSubFolder(NetworkRunner.BuildTypes referenceBuildModes) =>
 146        referenceBuildModes switch {
 147          NetworkRunner.BuildTypes.Debug   => "Debug",
 148          NetworkRunner.BuildTypes.Release => "Release",
 149          _                                => throw new ArgumentOutOfRangeException()
 150        };
 151    }
 152  }
 153}
 154
 155#endregion
 156
 157
 158#region Assets/Photon/Fusion/Editor/ChildLookupEditor.cs
 159
 160// removed July 12 2021
 161
 162
 163#endregion
 164
 165
 166#region Assets/Photon/Fusion/Editor/CustomTypes/FixedBufferPropertyAttributeDrawer.cs
 167
 168namespace Fusion.Editor {
 169
 170  using System;
 171  using System.Collections.Generic;
 172  using System.Linq;
 173  using System.Reflection;
 174  using Fusion.Internal;
 175  using Unity.Collections.LowLevel.Unsafe;
 176  using UnityEditor;
 177  using UnityEditor.Compilation;
 178  using UnityEngine;
 179
 180  [CustomPropertyDrawer(typeof(FixedBufferPropertyAttribute))]
 181  unsafe class FixedBufferPropertyAttributeDrawer : PropertyDrawerWithErrorHandling {
 182    public const string FixedBufferFieldName = "Data";
 183    public const string WrapperSurrogateDataPath = "Surrogate.Data";
 184
 185    private const float SpacingSubLabel = 2;
 186    private static readonly int _multiFieldPrefixId = "MultiFieldPrefixId".GetHashCode();
 187    private static int[] _buffer = Array.Empty<int>();
 188
 189    private static SurrogatePool _pool = new SurrogatePool();
 190    private static GUIContent[] _vectorProperties = new[] {
 191      new GUIContent("X"),
 192      new GUIContent("Y"),
 193      new GUIContent("Z"),
 194      new GUIContent("W"),
 195    };
 196
 197    private Dictionary<string, bool> _needsSurrogateCache = new Dictionary<string, bool>();
 198    private Dictionary<Type, UnitySurrogateBase> _optimisedReaderWriters = new Dictionary<Type, UnitySurrogateBase>();
 199
 200    private Type ActualFieldType => ((FixedBufferPropertyAttribute)attribute).Type;
 201    private int Capacity         => ((FixedBufferPropertyAttribute)attribute).Capacity;
 202    private Type SurrogateType   => ((FixedBufferPropertyAttribute)attribute).SurrogateType;
 203
 204    public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
 205      if (SurrogateType == null) {
 206        return EditorGUIUtility.singleLineHeight;
 207      }
 208
 209      if (NeedsSurrogate(property)) {
 210        var fixedBufferProperty = GetFixedBufferProperty(property);
 211        var firstElement = fixedBufferProperty.GetFixedBufferElementAtIndex(0);
 212        if (!firstElement.IsArrayElement()) {
 213          // it seems that with multiple seclection child elements are not accessible
 214          Debug.Assert(property.serializedObject.targetObjects.Length > 1);
 215          return EditorGUIUtility.singleLineHeight;
 216        }
 217
 218        var wrapper = _pool.Acquire(fieldInfo, Capacity, property, SurrogateType);
 219        try {
 220          return EditorGUI.GetPropertyHeight(wrapper.Property);
 221        } catch (Exception ex) {
 222          FusionEditorLog.ErrorInspector($"Error in GetPropertyHeight for {property.propertyPath}: {ex}");
 223          return EditorGUIUtility.singleLineHeight;
 224        }
 225
 226      } else {
 227        int count = 1;
 228        if (!EditorGUIUtility.wideMode) {
 229          count++;
 230        }
 231        return count * (EditorGUIUtility.singleLineHeight) + (count - 1) * EditorGUIUtility.standardVerticalSpacing;
 232      }
 233    }
 234
 235    protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 236      if (NeedsSurrogate(property)) {
 237        if (SurrogateType == null) {
 238          this.SetInfo($"[Networked] properties of type {ActualFieldType.FullName} in structs are not yet supported");
 239          EditorGUI.LabelField(position, label, GUIContent.none);
 240        } else {
 241          int capacity = Capacity;
 242          var fixedBufferProperty = GetFixedBufferProperty(property);
 243
 244          Array.Resize(ref _buffer, Math.Max(_buffer.Length, fixedBufferProperty.fixedBufferSize));
 245
 246          var firstElement = fixedBufferProperty.GetFixedBufferElementAtIndex(0);
 247          if (!firstElement.IsArrayElement()) {
 248            Debug.Assert(property.serializedObject.targetObjects.Length > 1);
 249            SetInfo($"Type does not support multi-edit");
 250            EditorGUI.LabelField(position, label);
 251          } else {
 252            var wrapper = _pool.Acquire(fieldInfo, Capacity, property, SurrogateType);
 253
 254            {
 255              bool surrogateOutdated = false;
 256              var targetObjects = property.serializedObject.targetObjects;
 257              if (targetObjects.Length > 1) {
 258                for (int i = 0; i < targetObjects.Length; ++i) {
 259                  using (var so = new SerializedObject(targetObjects[i])) {
 260                    using (var sp = so.FindPropertyOrThrow($"{property.propertyPath}.Data")) {
 261                      if (UpdateSurrogateFromFixedBuffer(sp, wrapper.Surrogates[i], false, _pool.Flush)) {
 262                        surrogateOutdated = true;
 263                      }
 264                    }
 265                  }
 266                }
 267
 268                if (surrogateOutdated) {
 269                  // it seems that a mere Update won't do here
 270                  wrapper.Property = new SerializedObject(wrapper.Wrappers).FindPropertyOrThrow(WrapperSurrogateDataPath
 271                }
 272              } else {
 273                // an optimised path, no alloc needed
 274                Debug.Assert(wrapper.Surrogates.Length == 1);
 275                if (UpdateSurrogateFromFixedBuffer(fixedBufferProperty, wrapper.Surrogates[0], false, _pool.Flush)) {
 276                  wrapper.Property.serializedObject.Update();
 277                }
 278              }
 279            }
 280
 281            // check if there has been any chagnes
 282            EditorGUI.BeginChangeCheck();
 283            EditorGUI.BeginProperty(position, label, property);
 284
 285            try {
 286              EditorGUI.PropertyField(position, wrapper.Property, label, true);
 287            } catch (Exception ex) {
 288              FusionEditorLog.ErrorInspector($"Error in OnGUIInternal for {property.propertyPath}: {ex}");
 289            }
 290
 291            EditorGUI.EndProperty();
 292            if (EditorGUI.EndChangeCheck()) {
 293              wrapper.Property.serializedObject.ApplyModifiedProperties();
 294
 295              // when not having multiple different values, just write the whole thing
 296              if (UpdateSurrogateFromFixedBuffer(fixedBufferProperty, wrapper.Surrogates[0], true, !fixedBufferProperty.
 297                fixedBufferProperty.serializedObject.ApplyModifiedProperties();
 298
 299                // refresh?
 300                wrapper.Property.serializedObject.Update();
 301              }
 302            }
 303          }
 304        }
 305      } else {
 306        if (!_optimisedReaderWriters.TryGetValue(SurrogateType, out var surrogate)) {
 307          surrogate = (UnitySurrogateBase)Activator.CreateInstance(SurrogateType);
 308          _optimisedReaderWriters.Add(SurrogateType, surrogate);
 309        }
 310
 311        if (ActualFieldType == typeof(float)) {
 312          DoFloatField(position, property, label, (IUnityValueSurrogate<float>)surrogate);
 313        } else if (ActualFieldType == typeof(Vector2)) {
 314          DoFloatVectorProperty(position, property, label, 2, (IUnityValueSurrogate<Vector2>)surrogate);
 315        } else if (ActualFieldType == typeof(Vector3)) {
 316          DoFloatVectorProperty(position, property, label, 3, (IUnityValueSurrogate<Vector3>)surrogate);
 317        } else if (ActualFieldType == typeof(Vector4)) {
 318          DoFloatVectorProperty(position, property, label, 4, (IUnityValueSurrogate<Vector4>)surrogate);
 319        }
 320      }
 321    }
 322
 323    private void DoFloatField(Rect position, SerializedProperty property, GUIContent label, IUnityValueSurrogate<float> 
 324      var fixedBuffer = GetFixedBufferProperty(property);
 325      Debug.Assert(1 == fixedBuffer.fixedBufferSize);
 326
 327      var valueProp = fixedBuffer.GetFixedBufferElementAtIndex(0);
 328      int value = valueProp.intValue;
 329      surrogate.Read(&value, 1);
 330
 331      EditorGUI.BeginProperty(position, label, property);
 332      EditorGUI.BeginChangeCheck();
 333      surrogate.DataProperty = EditorGUI.FloatField(position, label, surrogate.DataProperty);
 334      if (EditorGUI.EndChangeCheck()) {
 335        surrogate.Write(&value, 1);
 336        valueProp.intValue = value;
 337        property.serializedObject.ApplyModifiedProperties();
 338      }
 339      EditorGUI.EndProperty();
 340    }
 341
 342    private unsafe void DoFloatVectorProperty<T>(Rect position, SerializedProperty property, GUIContent label, int count
 343      EditorGUI.BeginProperty(position, label, property);
 344      try {
 345        var fixedBuffer = GetFixedBufferProperty(property);
 346        Debug.Assert(count == fixedBuffer.fixedBufferSize);
 347
 348        int* raw = stackalloc int[count];
 349        for (int i = 0; i < count; ++i) {
 350          raw[i] = fixedBuffer.GetFixedBufferElementAtIndex(i).intValue;
 351        }
 352
 353        readerWriter.Read(raw, 1);
 354
 355        int changed = 0;
 356
 357        var data = readerWriter.DataProperty;
 358        float* pdata = (float*)&data;
 359
 360        int id = GUIUtility.GetControlID(_multiFieldPrefixId, FocusType.Keyboard, position);
 361        position = UnityInternal.EditorGUI.MultiFieldPrefixLabel(position, id, label, count);
 362        if (position.width > 1) {
 363          using (new EditorGUI.IndentLevelScope(-EditorGUI.indentLevel)) {
 364            float w = (position.width - (count - 1) * SpacingSubLabel) / count;
 365            var nestedPosition = new Rect(position) { width = w };
 366
 367            for (int i = 0; i < count; ++i) {
 368              var propLabel = _vectorProperties[i];
 369              float prefixWidth = EditorStyles.label.CalcSize(propLabel).x;
 370              using (new FusionEditorGUI.LabelWidthScope(prefixWidth)) {
 371                EditorGUI.BeginChangeCheck();
 372                var newValue = propLabel == null ? EditorGUI.FloatField(nestedPosition, pdata[i]) : EditorGUI.FloatField
 373                if (EditorGUI.EndChangeCheck()) {
 374                  changed |= (1 << i);
 375                  pdata[i] = newValue;
 376                }
 377              }
 378              nestedPosition.x += w + SpacingSubLabel;
 379            }
 380          }
 381        }
 382
 383        if (changed != 0) {
 384          readerWriter.DataProperty = data;
 385          readerWriter.Write(raw, 1);
 386
 387          for (int i = 0; i < count; ++i) {
 388            if ((changed & (1 << i)) != 0) {
 389              fixedBuffer.GetFixedBufferElementAtIndex(i).intValue = raw[i];
 390            }
 391          }
 392          property.serializedObject.ApplyModifiedProperties();
 393        }
 394      } finally {
 395        EditorGUI.EndProperty();
 396      }
 397    }
 398
 399    private SerializedProperty GetFixedBufferProperty(SerializedProperty prop) {
 400      var result = prop.FindPropertyRelativeOrThrow(FixedBufferFieldName);
 401      Debug.Assert(result.isFixedBuffer);
 402      return result;
 403    }
 404
 405    private bool NeedsSurrogate(SerializedProperty property) {
 406      if (_needsSurrogateCache.TryGetValue(property.propertyPath, out var result)) {
 407        return result;
 408      }
 409
 410      result = true;
 411      if (ActualFieldType == typeof(float) || ActualFieldType == typeof(Vector2) || ActualFieldType == typeof(Vector3) |
 412        var attributes = UnityInternal.ScriptAttributeUtility.GetFieldAttributes(fieldInfo);
 413        if (attributes == null || attributes.Count == 0) {
 414          // fast drawers do not support any additional attributes
 415          result = false;
 416        }
 417      }
 418
 419      _needsSurrogateCache.Add(property.propertyPath, result);
 420      return result;
 421    }
 422
 423    private bool UpdateSurrogateFromFixedBuffer(SerializedProperty sp, UnitySurrogateBase surrogate, bool write, bool fo
 424      int count = sp.fixedBufferSize;
 425      Array.Resize(ref _buffer, Math.Max(_buffer.Length, count));
 426
 427      // need to get to the first property... `GetFixedBufferElementAtIndex` is slow and allocs
 428
 429      var element = sp.Copy();
 430      element.Next(true); // .Array
 431      element.Next(true); // .Array.size
 432      element.Next(true); // .Array.data[0]
 433
 434      fixed (int* p = _buffer) {
 435        UnsafeUtility.MemClear(p, count * sizeof(int));
 436
 437        try {
 438          surrogate.Write(p, Capacity);
 439        } catch (Exception ex) {
 440          SetError($"Failed writing: {ex}");
 441        }
 442
 443        int i = 0;
 444        if (!force) {
 445          // find first difference
 446          for (; i < count; ++i, element.Next(true)) {
 447            Debug.Assert(element.propertyType == SerializedPropertyType.Integer);
 448            if (element.intValue != p[i]) {
 449              break;
 450            }
 451          }
 452        }
 453
 454        if (i < count) {
 455          // update data
 456          if (write) {
 457            for (; i < count; ++i, element.Next(true)) {
 458              element.intValue = p[i];
 459            }
 460          } else {
 461            for (; i < count; ++i, element.Next(true)) {
 462              p[i] = element.intValue;
 463            }
 464          }
 465          // update surrogate
 466          surrogate.Read(p, Capacity);
 467          return true;
 468        } else {
 469          return false;
 470        }
 471      }
 472    }
 473
 474    private class SurrogatePool {
 475
 476      private const int MaxTTL = 10;
 477
 478      private FieldInfo _surrogateField = typeof(FusionUnitySurrogateBaseWrapper).GetField(nameof(FusionUnitySurrogateBa
 479      private Dictionary<(Type, string, int), PropertyEntry> _used = new Dictionary<(Type, string, int), PropertyEntry>(
 480      private Dictionary<Type, Stack<FusionUnitySurrogateBaseWrapper>> _wrappersPool = new Dictionary<Type, Stack<Fusion
 481
 482      public SurrogatePool() {
 483        Undo.undoRedoPerformed += () => Flush = true;
 484
 485        EditorApplication.update += () => {
 486          Flush = false;
 487          if (!WasUsed) {
 488            return;
 489          }
 490          WasUsed = false;
 491
 492          var keysToRemove = new List<(Type, string, int)>();
 493
 494          foreach (var kv in _used) {
 495            var entry = kv.Value;
 496            if (--entry.TTL < 0) {
 497              // return to pool
 498              keysToRemove.Add(kv.Key);
 499              foreach (var wrapper in entry.Wrappers) {
 500                _wrappersPool[wrapper.Surrogate.GetType()].Push(wrapper);
 501              }
 502            }
 503          }
 504
 505          // make all the wrappers available again
 506          foreach (var key in keysToRemove) {
 507            FusionEditorLog.TraceInspector($"Cleaning up {key}");
 508            _used.Remove(key);
 509          }
 510        };
 511
 512        CompilationPipeline.compilationFinished += obj => {
 513          // destroy SO's, we don't want them to hold on to the surrogates
 514
 515          var wrappers = _wrappersPool.Values.SelectMany(x => x)
 516            .Concat(_used.Values.SelectMany(x => x.Wrappers));
 517
 518          foreach (var wrapper in wrappers) {
 519            UnityEngine.Object.DestroyImmediate(wrapper);
 520          }
 521        };
 522      }
 523
 524      public bool Flush { get; private set; }
 525
 526      public bool WasUsed { get; private set; }
 527
 528      public PropertyEntry Acquire(FieldInfo field, int capacity, SerializedProperty property, Type type) {
 529        WasUsed = true;
 530
 531        bool hadNulls = false;
 532
 533        var key = (type, property.propertyPath, property.serializedObject.targetObjects.Length);
 534        if (_used.TryGetValue(key, out var entry)) {
 535          var countValid = entry.Wrappers.Count(x => x);
 536          if (countValid != entry.Wrappers.Length) {
 537            // something destroyed wrappers
 538            Debug.Assert(countValid == 0);
 539            _used.Remove(key);
 540            hadNulls = true;
 541          } else {
 542            entry.TTL = MaxTTL;
 543            return entry;
 544          }
 545        }
 546
 547        // acquire new entry
 548        var wrappers = new FusionUnitySurrogateBaseWrapper[key.Item3];
 549        if (!_wrappersPool.TryGetValue(type, out var pool)) {
 550          pool = new Stack<FusionUnitySurrogateBaseWrapper>();
 551          _wrappersPool.Add(type, pool);
 552        }
 553
 554        for (int i = 0; i < wrappers.Length; ++i) {
 555
 556          // pop destroyed ones
 557          while (pool.Count > 0 && !pool.Peek()) {
 558            pool.Pop();
 559            hadNulls = true;
 560          }
 561
 562          if (pool.Count > 0) {
 563            wrappers[i] = pool.Pop();
 564          } else {
 565            FusionEditorLog.TraceInspector($"Allocating surrogate {type}");
 566            wrappers[i] = ScriptableObject.CreateInstance<FusionUnitySurrogateBaseWrapper>();
 567          }
 568
 569          if (wrappers[i].SurrogateType != type) {
 570            FusionEditorLog.TraceInspector($"Replacing type {wrappers[i].Surrogate?.GetType()} with {type}");
 571            wrappers[i].Surrogate = (UnitySurrogateBase)Activator.CreateInstance(type);
 572            wrappers[i].Surrogate.Init(capacity);
 573            wrappers[i].SurrogateType = type;
 574          }
 575        }
 576
 577        FusionEditorLog.TraceInspector($"Created entry for {property.propertyPath}");
 578
 579        entry = new PropertyEntry() {
 580          Property = new SerializedObject(wrappers).FindPropertyOrThrow(WrapperSurrogateDataPath),
 581          Surrogates = wrappers.Select(x => x.Surrogate).ToArray(),
 582          TTL = MaxTTL,
 583          Wrappers = wrappers
 584        };
 585
 586        _used.Add(key, entry);
 587
 588        if (hadNulls) {
 589          GUIUtility.ExitGUI();
 590        }
 591
 592        return entry;
 593      }
 594
 595      public class PropertyEntry {
 596        public SerializedProperty Property;
 597        public UnitySurrogateBase[] Surrogates;
 598        public int TTL;
 599        public FusionUnitySurrogateBaseWrapper[] Wrappers;
 600      }
 601    }
 602  }
 603}
 604
 605#endregion
 606
 607
 608#region Assets/Photon/Fusion/Editor/CustomTypes/INetworkPrefabSourceDrawer.cs
 609
 610namespace Fusion.Editor {
 611  using System;
 612  using UnityEditor;
 613  using UnityEngine;
 614
 615  [CustomPropertyDrawer(typeof(INetworkPrefabSource), true)]
 616  class INetworkPrefabSourceDrawer : PropertyDrawerWithErrorHandling {
 617
 618    const int ThumbnailWidth = 20;
 619
 620    protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 621
 622      using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out position)) {
 623
 624        EditorGUI.BeginChangeCheck();
 625
 626        var source = property.managedReferenceValue as INetworkPrefabSource;
 627        position = DrawThumbnailPrefix(position, source);
 628        source = DrawSourceObjectPicker(position, GUIContent.none, source);
 629
 630        if (EditorGUI.EndChangeCheck()) {
 631          // see how it can be loaded
 632          property.managedReferenceValue = source;
 633          property.serializedObject.ApplyModifiedProperties();
 634        }
 635      }
 636    }
 637
 638    public static Rect DrawThumbnailPrefix(Rect position, INetworkPrefabSource source) {
 639      if (source == null) {
 640        return position;
 641      }
 642
 643      var pos = position;
 644      pos.width = ThumbnailWidth;
 645      FusionEditorGUI.DrawTypeThumbnail(pos, source.GetType(), "NetworkPrefabSource", source.Description);
 646      position.xMin += ThumbnailWidth;
 647      return position;
 648    }
 649
 650    public static void DrawThumbnail(Rect position, INetworkPrefabSource source) {
 651      if (source == null) {
 652        return;
 653      }
 654      var pos = position;
 655      pos.x += (pos.width - ThumbnailWidth) / 2;
 656      pos.width = ThumbnailWidth;
 657      FusionEditorGUI.DrawTypeThumbnail(pos, source.GetType(), "NetworkPrefabSource", source.Description);
 658    }
 659
 660    public static INetworkPrefabSource DrawSourceObjectPicker(Rect position, GUIContent label, INetworkPrefabSource sour
 661      NetworkProjectConfigUtilities.TryGetPrefabEditorInstance(source?.AssetGuid ?? default, out var target);
 662
 663      EditorGUI.BeginChangeCheck();
 664      target = NetworkPrefabRefDrawer.DrawNetworkPrefabPicker(position, label, target);
 665      if (EditorGUI.EndChangeCheck()) {
 666        if (target) {
 667          var factory = new NetworkAssetSourceFactory();
 668          return factory.TryCreatePrefabSource(new NetworkAssetSourceFactoryContext(target));
 669        } else {
 670          return null;
 671        }
 672      } else {
 673        return source;
 674      }
 675    }
 676
 677    public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
 678      return EditorGUIUtility.singleLineHeight;
 679    }
 680  }
 681}
 682
 683
 684#endregion
 685
 686
 687#region Assets/Photon/Fusion/Editor/CustomTypes/NetworkBoolDrawer.cs
 688
 689namespace Fusion.Editor {
 690  using System;
 691  using System.Collections.Generic;
 692  using System.Reflection;
 693  using UnityEditor;
 694  using UnityEngine;
 695
 696  [CustomPropertyDrawer(typeof(NetworkBool))]
 697  public class NetworkBoolDrawer : PropertyDrawer {
 698    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
 699      using (new FusionEditorGUI.PropertyScope(position, label, property)) {
 700        var valueProperty = property.FindPropertyRelativeOrThrow("_value");
 701        EditorGUI.BeginChangeCheck();
 702        bool isChecked = EditorGUI.Toggle(position, label, valueProperty.intValue > 0);
 703        if (EditorGUI.EndChangeCheck()) {
 704          valueProperty.intValue = isChecked ? 1 : 0;
 705          valueProperty.serializedObject.ApplyModifiedProperties();
 706        }
 707      }
 708    }
 709  }
 710}
 711
 712#endregion
 713
 714
 715#region Assets/Photon/Fusion/Editor/CustomTypes/NetworkObjectGuidDrawer.cs
 716
 717namespace Fusion.Editor {
 718  using UnityEditor;
 719  using UnityEngine;
 720
 721  [CustomPropertyDrawer(typeof(NetworkObjectGuid))]
 722  [FusionPropertyDrawerMeta(HasFoldout = false)]
 723  class NetworkObjectGuidDrawer : PropertyDrawerWithErrorHandling {
 724
 725    protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 726      var guid = GetValue(property);
 727
 728      using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out position)) {
 729        if (!GUI.enabled) {
 730          GUI.enabled = true;
 731          EditorGUI.SelectableLabel(position, $"{(System.Guid)guid}");
 732          GUI.enabled = false;
 733        } else {
 734          EditorGUI.BeginChangeCheck();
 735
 736          var text = EditorGUI.TextField(position, ((System.Guid)guid).ToString());
 737          ClearErrorIfLostFocus();
 738
 739          if (EditorGUI.EndChangeCheck()) {
 740            if (NetworkObjectGuid.TryParse(text, out guid)) {
 741              SetValue(property, guid);
 742              property.serializedObject.ApplyModifiedProperties();
 743            } else {
 744              SetError($"Unable to parse {text}");
 745            }
 746          }
 747        }
 748      }
 749    }
 750
 751    public static unsafe NetworkObjectGuid GetValue(SerializedProperty property) {
 752      var guid = new NetworkObjectGuid();
 753      var prop = property.FindPropertyRelativeOrThrow(nameof(NetworkObjectGuid.RawGuidValue));
 754        guid.RawGuidValue[0] = prop.GetFixedBufferElementAtIndex(0).longValue;
 755        guid.RawGuidValue[1] = prop.GetFixedBufferElementAtIndex(1).longValue;
 756      return guid;
 757    }
 758
 759    public static unsafe void SetValue(SerializedProperty property, NetworkObjectGuid guid) {
 760      var prop = property.FindPropertyRelativeOrThrow(nameof(NetworkObjectGuid.RawGuidValue));
 761        prop.GetFixedBufferElementAtIndex(0).longValue = guid.RawGuidValue[0];
 762        prop.GetFixedBufferElementAtIndex(1).longValue = guid.RawGuidValue[1];
 763    }
 764  }
 765}
 766
 767#endregion
 768
 769
 770#region Assets/Photon/Fusion/Editor/CustomTypes/NetworkPrefabAttributeDrawer.cs
 771
 772namespace Fusion.Editor {
 773  using System;
 774  using System.Collections.Generic;
 775  using System.Reflection;
 776  using UnityEditor;
 777  using UnityEngine;
 778
 779  [CustomPropertyDrawer(typeof(NetworkPrefabAttribute))]
 780  [FusionPropertyDrawerMeta(HasFoldout = false)]
 781  class NetworkPrefabAttributeDrawer : PropertyDrawerWithErrorHandling {
 782
 783    protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 784
 785      var leafType = fieldInfo.FieldType.GetUnityLeafType();
 786      if (leafType != typeof(GameObject) && leafType != typeof(NetworkObject) && !leafType.IsSubclassOf(typeof(NetworkOb
 787        SetError($"{nameof(NetworkPrefabAttribute)} only works for {typeof(GameObject)} and {typeof(NetworkObject)} fiel
 788        return;
 789      }
 790
 791      using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out position)) {
 792
 793        GameObject prefab;
 794        if (leafType == typeof(GameObject)) {
 795          prefab = (GameObject)property.objectReferenceValue;
 796        } else {
 797          var component = (NetworkObject)property.objectReferenceValue;
 798          prefab = component != null ? component.gameObject : null;
 799        }
 800
 801        EditorGUI.BeginChangeCheck();
 802
 803        prefab = (GameObject)EditorGUI.ObjectField(position, prefab, typeof(GameObject), false);
 804
 805        // ensure the results are filtered
 806        if (UnityInternal.ObjectSelector.isVisible) {
 807          var selector = UnityInternal.ObjectSelector.get;
 808          if (UnityInternal.EditorGUIUtility.LastControlID == selector.objectSelectorID) {
 809            var filter = selector.searchFilter;
 810            if (!filter.Contains(NetworkProjectConfigImporter.FusionPrefabTagSearchTerm)) {
 811              if (string.IsNullOrEmpty(filter)) {
 812                filter = NetworkProjectConfigImporter.FusionPrefabTagSearchTerm;
 813              } else {
 814                filter = NetworkProjectConfigImporter.FusionPrefabTagSearchTerm + " " + filter;
 815              }
 816              selector.searchFilter = filter;
 817            }
 818          }
 819        }
 820
 821        if (EditorGUI.EndChangeCheck()) {
 822          UnityEngine.Object result;
 823          if (!prefab) {
 824            result = null;
 825          } else {
 826            if (leafType == typeof(GameObject)) {
 827              result = prefab;
 828            } else {
 829              result = prefab.GetComponent(leafType);
 830              if (!result) {
 831                SetError($"Prefab {prefab} does not have a {leafType} component");
 832                return;
 833              }
 834            }
 835          }
 836
 837          property.objectReferenceValue = prefab;
 838          property.serializedObject.ApplyModifiedProperties();
 839        }
 840
 841        if (prefab) {
 842          var no = prefab.GetComponent<NetworkObject>();
 843          if (!no) {
 844            SetError($"Prefab {prefab} does not have a {nameof(NetworkObject)} component");
 845          }
 846          if (!AssetDatabaseUtils.HasLabel(prefab, NetworkProjectConfigImporter.FusionPrefabTag)) {
 847            SetError($"Prefab {prefab} is not tagged as a Fusion prefab. Try reimporting.");
 848          }
 849        }
 850      }
 851    }
 852  }
 853}
 854
 855#endregion
 856
 857
 858#region Assets/Photon/Fusion/Editor/CustomTypes/NetworkPrefabRefDrawer.cs
 859
 860namespace Fusion.Editor {
 861  using System;
 862  using System.Collections.Generic;
 863  using System.Reflection;
 864  using UnityEditor;
 865  using UnityEngine;
 866
 867  [CustomPropertyDrawer(typeof(NetworkPrefabRef))]
 868  [FusionPropertyDrawerMeta(HasFoldout = false)]
 869  class NetworkPrefabRefDrawer : PropertyDrawerWithErrorHandling {
 870
 871    protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 872
 873      var prefabRef = NetworkObjectGuidDrawer.GetValue(property);
 874
 875      using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out position)) {
 876        NetworkObject prefab = null;
 877        if (prefabRef.IsValid && !NetworkProjectConfigUtilities.TryGetPrefabEditorInstance(prefabRef, out prefab)) {
 878          SetError($"Prefab with guid {prefabRef} not found.");
 879        }
 880
 881        EditorGUI.BeginChangeCheck();
 882
 883        prefab = DrawNetworkPrefabPicker(position, GUIContent.none, prefab);
 884
 885        if (EditorGUI.EndChangeCheck()) {
 886          if (prefab) {
 887            prefabRef = NetworkObjectEditor.GetPrefabGuid(prefab);
 888          } else {
 889            prefabRef = default;
 890          }
 891          NetworkObjectGuidDrawer.SetValue(property, prefabRef);
 892          property.serializedObject.ApplyModifiedProperties();
 893        }
 894
 895        SetInfo($"{prefabRef}");
 896
 897
 898        if (prefab) {
 899          var expectedPrefabRef = NetworkObjectEditor.GetPrefabGuid(prefab);
 900          if (!prefabRef.Equals(expectedPrefabRef)) {
 901            SetError($"Resolved {prefab} has a different guid ({expectedPrefabRef}) than expected ({prefabRef}). " +
 902              $"This can happen if prefabs are incorrectly resolved, e.g. when there are multiple resources of the same 
 903          } else if (!expectedPrefabRef.IsValid) {
 904            SetError($"Prefab {prefab} needs to be reimported.");
 905          } else if (!AssetDatabaseUtils.HasLabel(prefab, NetworkProjectConfigImporter.FusionPrefabTag)) {
 906            SetError($"Prefab {prefab} is not tagged as a Fusion prefab. Try reimporting.");
 907          } else {
 908            // ClearError();
 909          }
 910        }
 911      }
 912    }
 913
 914    public static NetworkObject DrawNetworkPrefabPicker(Rect position, GUIContent label, NetworkObject prefab) {
 915      var prefabGo = (GameObject)EditorGUI.ObjectField(position, label, prefab ? prefab.gameObject : null, typeof(GameOb
 916
 917      // ensure the results are filtered
 918      if (UnityInternal.ObjectSelector.isVisible) {
 919        var selector = UnityInternal.ObjectSelector.get;
 920        if (UnityInternal.EditorGUIUtility.LastControlID == selector.objectSelectorID) {
 921          var filter = selector.searchFilter;
 922          if (!filter.Contains(NetworkProjectConfigImporter.FusionPrefabTagSearchTerm)) {
 923            if (string.IsNullOrEmpty(filter)) {
 924              filter = NetworkProjectConfigImporter.FusionPrefabTagSearchTerm;
 925            } else {
 926              filter = NetworkProjectConfigImporter.FusionPrefabTagSearchTerm + " " + filter;
 927            }
 928
 929            selector.searchFilter = filter;
 930          }
 931        }
 932      }
 933
 934      if (prefabGo) {
 935        return prefabGo.GetComponent<NetworkObject>();
 936      } else {
 937        return null;
 938      }
 939    }
 940  }
 941}
 942
 943#endregion
 944
 945
 946#region Assets/Photon/Fusion/Editor/CustomTypes/NetworkStringDrawer.cs
 947
 948namespace Fusion.Editor {
 949  using System;
 950  using System.Collections.Generic;
 951  using System.Reflection;
 952  using System.Text;
 953  using UnityEditor;
 954  using UnityEngine;
 955
 956  [CustomPropertyDrawer(typeof(NetworkString<>))]
 957  [FusionPropertyDrawerMeta(HasFoldout = false)]
 958  class NetworkStringDrawer : PropertyDrawerWithErrorHandling {
 959
 960    private string _str = "";
 961    private Action<int[], int> _write;
 962    private Action<int[], int> _read;
 963    private int _expectedLength;
 964
 965    public NetworkStringDrawer() {
 966      _write = (buffer, count) => {
 967        unsafe {
 968          fixed (int* p = buffer) {
 969            _str = new string((sbyte*)p, 0, Mathf.Clamp(_expectedLength, 0, count) * 4, Encoding.UTF32);
 970          }
 971        }
 972      };
 973
 974      _read = (buffer, count) => {
 975        unsafe {
 976          fixed (int* p = buffer) {
 977            var charCount = UTF32Tools.Convert(_str, (uint*)p, count).CharacterCount;
 978            if (charCount < _str.Length) {
 979              _str = _str.Substring(0, charCount);
 980            }
 981          }
 982        }
 983      };
 984    }
 985
 986    protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 987
 988      var length = property.FindPropertyRelativeOrThrow(nameof(NetworkString<_2>._length));
 989      var data = property.FindPropertyRelativeOrThrow($"{nameof(NetworkString<_2>._data)}.Data");
 990
 991      _expectedLength = length.intValue;
 992      data.UpdateFixedBuffer(_read, _write, false);
 993
 994      EditorGUI.BeginChangeCheck();
 995
 996      using (new FusionEditorGUI.ShowMixedValueScope(data.hasMultipleDifferentValues)) {
 997        _str = EditorGUI.TextField(position, label, _str);
 998      }
 999
 1000      if (EditorGUI.EndChangeCheck()) {
 1001        _expectedLength = _str.Length;
 1002        if (data.UpdateFixedBuffer(_read, _write, true, data.hasMultipleDifferentValues)) {
 1003          length.intValue = Encoding.UTF32.GetByteCount(_str) / 4;
 1004          data.serializedObject.ApplyModifiedProperties();
 1005        }
 1006      }
 1007    }
 1008  }
 1009}
 1010
 1011#endregion
 1012
 1013
 1014#region Assets/Photon/Fusion/Editor/CustomTypes/NormalizedRectAttributeDrawer.cs
 1015
 1016
 1017namespace Fusion.Editor {
 1018  using System;
 1019  using UnityEditor;
 1020  using UnityEngine;
 1021
 1022#if UNITY_EDITOR
 1023  [CustomPropertyDrawer(typeof(NormalizedRectAttribute))]
 1024  public class NormalizedRectAttributeDrawer : PropertyDrawer {
 1025
 1026    bool isDragNewRect;
 1027    bool isDragXMin, isDragXMax, isDragYMin, isDragYMax, isDragAll;
 1028    MouseCursor lockCursorStyle;
 1029
 1030    Vector2 mouseDownStart;
 1031    static GUIStyle _compactLabelStyle;
 1032    static GUIStyle _compactValueStyle;
 1033
 1034    const float EXPANDED_HEIGHT = 140;
 1035    const float COLLAPSE_HEIGHT = 48;
 1036
 1037    public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
 1038      if (property.propertyType == SerializedPropertyType.Rect) {
 1039        return property.isExpanded ? EXPANDED_HEIGHT : COLLAPSE_HEIGHT;
 1040      } else {
 1041        return base.GetPropertyHeight(property, label);
 1042      }
 1043    }
 1044
 1045    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
 1046
 1047      EditorGUI.BeginProperty(position, label, property);
 1048
 1049      bool hasChanged = false;
 1050
 1051      EditorGUI.LabelField(new Rect(position) { height = 17 }, label);
 1052
 1053      var value = property.rectValue;
 1054
 1055      if (property.propertyType == SerializedPropertyType.Rect) {
 1056
 1057        var dragarea = new Rect(position) {
 1058          yMin = position.yMin + 16 + 3,
 1059          yMax = position.yMax - 2,
 1060          //xMin = position.xMin + 16,
 1061          //xMax = position.xMax - 4
 1062        };
 1063
 1064        // lower foldout box
 1065        GUI.Box(dragarea, GUIContent.none, EditorStyles.helpBox);
 1066
 1067        property.isExpanded = GUI.Toggle(new Rect(position) { xMin = dragarea.xMin + 2, yMin = dragarea.yMin + 2, width 
 1068        bool isExpanded = property.isExpanded;
 1069
 1070        float border = isExpanded ? 4 : 2;
 1071        dragarea.xMin += 18;
 1072        dragarea.yMin += border;
 1073        dragarea.xMax -= border;
 1074        dragarea.yMax -= border;
 1075
 1076        // Reshape the inner box to the correct aspect ratio
 1077        if (isExpanded) {
 1078          var ratio = (attribute as NormalizedRectAttribute).AspectRatio;
 1079          if (ratio == 0) {
 1080            var currentRes = UnityEditor.Handles.GetMainGameViewSize();
 1081            ratio = currentRes.x / currentRes.y;
 1082          }
 1083
 1084          // Don't go any wider than the inspector box.
 1085          var width = (dragarea.height * ratio);
 1086          if (width < dragarea.width) {
 1087            var x = (dragarea.width - width) / 2;
 1088            dragarea.x = dragarea.xMin + (int)x;
 1089            dragarea.width = (int)(width);
 1090          }
 1091        }
 1092
 1093
 1094        // Simulated desktop rect
 1095        GUI.Box(dragarea, GUIContent.none, EditorStyles.helpBox);
 1096
 1097        var invertY = (attribute as NormalizedRectAttribute).InvertY;
 1098
 1099        Event e = Event.current;
 1100
 1101        const int HANDLE_SIZE = 8;
 1102
 1103        var normmin = new Vector2(value.xMin, invertY ? 1f - value.yMin : value.yMin);
 1104        var normmax = new Vector2(value.xMax, invertY ? 1f - value.yMax : value.yMax);
 1105        var minreal = Rect.NormalizedToPoint(dragarea, normmin);
 1106        var maxreal = Rect.NormalizedToPoint(dragarea, normmax);
 1107        var lowerleftrect = new Rect(minreal.x              , minreal.y - (invertY ? HANDLE_SIZE : 0), HANDLE_SIZE, HAND
 1108        var upperrghtrect = new Rect(maxreal.x - HANDLE_SIZE, maxreal.y - (invertY ? 0 : HANDLE_SIZE), HANDLE_SIZE, HAND
 1109        var upperleftrect = new Rect(minreal.x              , maxreal.y - (invertY ? 0 : HANDLE_SIZE), HANDLE_SIZE, HAND
 1110        var lowerrghtrect = new Rect(maxreal.x - HANDLE_SIZE, minreal.y - (invertY ? HANDLE_SIZE : 0), HANDLE_SIZE, HAND
 1111
 1112        var currentrect = Rect.MinMaxRect(minreal.x, invertY ? maxreal.y : minreal.y, maxreal.x, invertY ? minreal.y : m
 1113
 1114        if (lockCursorStyle == MouseCursor.Arrow) {
 1115          if (isExpanded) {
 1116            EditorGUIUtility.AddCursorRect(lowerleftrect, MouseCursor.Link);
 1117            EditorGUIUtility.AddCursorRect(upperrghtrect, MouseCursor.Link);
 1118            EditorGUIUtility.AddCursorRect(upperleftrect, MouseCursor.Link);
 1119            EditorGUIUtility.AddCursorRect(lowerrghtrect, MouseCursor.Link);
 1120          }
 1121          EditorGUIUtility.AddCursorRect(currentrect, MouseCursor.MoveArrow);
 1122        } else {
 1123          // Lock cursor to a style while dragging, otherwise the slow inspector update causes rapid mouse icon changes.
 1124          EditorGUIUtility.AddCursorRect(dragarea, lockCursorStyle);
 1125        }
 1126
 1127        EditorGUI.DrawRect(lowerleftrect, Color.yellow);
 1128        EditorGUI.DrawRect(upperrghtrect, Color.yellow);
 1129        EditorGUI.DrawRect(upperleftrect, Color.yellow);
 1130        EditorGUI.DrawRect(lowerrghtrect, Color.yellow);
 1131
 1132        var mousepos = e.mousePosition;
 1133        if (e.button == 0) {
 1134          if (e.type == EventType.MouseUp) {
 1135            isDragXMin = false;
 1136            isDragYMin = false;
 1137            isDragXMax = false;
 1138            isDragYMax = false;
 1139            isDragAll  = false;
 1140            lockCursorStyle = MouseCursor.Arrow;
 1141            isDragNewRect   = false;
 1142
 1143            hasChanged = true;
 1144          }
 1145
 1146          if (e.type == EventType.MouseDown ) {
 1147            if (isExpanded && lowerleftrect.Contains(mousepos)) {
 1148              isDragXMin = true;
 1149              isDragYMin = true;
 1150              lockCursorStyle = MouseCursor.Link;
 1151            } else if (isExpanded && upperrghtrect.Contains(mousepos)) {
 1152              isDragXMax = true;
 1153              isDragYMax = true;
 1154              lockCursorStyle = MouseCursor.Link;
 1155            } else if (isExpanded && upperleftrect.Contains(mousepos)) {
 1156              isDragXMin = true;
 1157              isDragYMax = true;
 1158              lockCursorStyle = MouseCursor.Link;
 1159            } else if (isExpanded && lowerrghtrect.Contains(mousepos)) {
 1160              isDragXMax = true;
 1161              isDragYMin = true;
 1162              lockCursorStyle = MouseCursor.Link;
 1163            } else if (currentrect.Contains(mousepos)) {
 1164              isDragAll = true;
 1165              // mouse start is stored as a normalized offset from the Min values.
 1166              mouseDownStart = Rect.PointToNormalized(dragarea, mousepos) - normmin;
 1167              lockCursorStyle = MouseCursor.MoveArrow;
 1168            } else if (isExpanded && dragarea.Contains(mousepos)) {
 1169              mouseDownStart = mousepos;
 1170              isDragNewRect = true;
 1171            }
 1172          }
 1173        }
 1174
 1175        if (e.type == EventType.MouseDrag) {
 1176
 1177          Rect rect;
 1178          if (isDragNewRect) {
 1179            var start = Rect.PointToNormalized(dragarea, mouseDownStart);
 1180            var end = Rect.PointToNormalized(dragarea, e.mousePosition);
 1181
 1182            if (invertY) {
 1183              rect = Rect.MinMaxRect(
 1184                  Math.Max(0f,      Math.Min(start.x, end.x)),
 1185                  Math.Max(0f, 1f - Math.Max(start.y, end.y)),
 1186                  Math.Min(1f,      Math.Max(start.x, end.x)),
 1187                  Math.Min(1f, 1f - Math.Min(start.y, end.y))
 1188                  );
 1189            } else {
 1190              rect = Rect.MinMaxRect(
 1191                  Math.Max(0f, Math.Min(start.x, end.x)),
 1192                  Math.Max(0f, Math.Min(start.y, end.y)),
 1193                  Math.Min(1f, Math.Max(start.x, end.x)),
 1194                  Math.Min(1f, Math.Max(start.y, end.y))
 1195                  );
 1196            }
 1197            property.rectValue = rect;
 1198            hasChanged = true;
 1199
 1200
 1201          } else if (isDragAll){
 1202            var normmouse = Rect.PointToNormalized(dragarea, e.mousePosition);
 1203            rect = new Rect(value) {
 1204              x = Math.Max(normmouse.x - mouseDownStart.x, 0),
 1205              y = Math.Max(invertY ? (1 - normmouse.y + mouseDownStart.y) : (normmouse.y - mouseDownStart.y), 0)
 1206            };
 1207
 1208            if (rect.xMax > 1) {
 1209              rect = new Rect(rect) { x = rect.x + (1f - rect.xMax)};
 1210            }
 1211            if (rect.yMax > 1) {
 1212              rect = new Rect(rect) { y = rect.y + (1f - rect.yMax) };
 1213            }
 1214
 1215            property.rectValue = rect;
 1216            hasChanged = true;
 1217
 1218          } else if (isDragXMin || isDragXMax || isDragYMin || isDragYMax) {
 1219
 1220            const float VERT_HANDLE_MIN_DIST = .2f;
 1221            const float HORZ_HANDLE_MIN_DIST = .05f;
 1222            var normmouse = Rect.PointToNormalized(dragarea, e.mousePosition);
 1223            if (invertY) {
 1224              rect = Rect.MinMaxRect(
 1225                isDragXMin ? Math.Min(     normmouse.x, value.xMax - HORZ_HANDLE_MIN_DIST) : value.xMin,
 1226                isDragYMin ? Math.Min(1f - normmouse.y, value.yMax - VERT_HANDLE_MIN_DIST) : value.yMin,
 1227                isDragXMax ? Math.Max(     normmouse.x, value.xMin + HORZ_HANDLE_MIN_DIST) : value.xMax,
 1228                isDragYMax ? Math.Max(1f - normmouse.y, value.yMin + VERT_HANDLE_MIN_DIST) : value.yMax
 1229                );
 1230            } else {
 1231              rect = Rect.MinMaxRect(
 1232                isDragXMin ? Math.Min(normmouse.x, value.xMax - HORZ_HANDLE_MIN_DIST) : value.xMin,
 1233                isDragYMin ? Math.Min(normmouse.y, value.yMax - VERT_HANDLE_MIN_DIST) : value.yMin,
 1234                isDragXMax ? Math.Max(normmouse.x, value.xMin + HORZ_HANDLE_MIN_DIST) : value.xMax,
 1235                isDragYMax ? Math.Max(normmouse.y, value.yMin + VERT_HANDLE_MIN_DIST) : value.yMax
 1236                );
 1237            }
 1238
 1239            property.rectValue = rect;
 1240            hasChanged = true;
 1241          }
 1242        }
 1243
 1244        const float SPACING = 4f;
 1245        const int LABELS_WIDTH = 16;
 1246        const float COMPACT_THRESHOLD = 340f;
 1247
 1248        bool useCompact = position.width < COMPACT_THRESHOLD;
 1249
 1250        var labelwidth = EditorGUIUtility.labelWidth;
 1251        var fieldwidth = (position.width - labelwidth- 3 * SPACING) * 0.25f ;
 1252        var fieldbase = new Rect(position) { xMin = position.xMin + labelwidth, height = 16, width = fieldwidth - (useCo
 1253
 1254        if (_compactValueStyle == null) {
 1255          _compactLabelStyle = new GUIStyle(EditorStyles.miniLabel)     { fontSize = 9, alignment = TextAnchor.MiddleLef
 1256          _compactValueStyle = new GUIStyle(EditorStyles.miniTextField) { fontSize = 9, alignment = TextAnchor.MiddleLef
 1257        }
 1258        GUIStyle valueStyle = _compactValueStyle;
 1259
 1260        //if (useCompact) {
 1261        //  if (_compactStyle == null) {
 1262        //    _compactStyle = new GUIStyle(EditorStyles.miniTextField) { fontSize = 9, alignment = TextAnchor.MiddleLeft
 1263        //  }
 1264        //  valueStyle = _compactStyle;
 1265        //} else {
 1266        //  valueStyle = EditorStyles.textField;
 1267        //}
 1268
 1269        // Only draw labels when not in compact
 1270        if (!useCompact) {
 1271          Rect l1 = new Rect(fieldbase) { x = fieldbase.xMin };
 1272          Rect l2 = new Rect(fieldbase) { x = fieldbase.xMin + 1 * (fieldwidth + SPACING) };
 1273          Rect l3 = new Rect(fieldbase) { x = fieldbase.xMin + 2 * (fieldwidth + SPACING) };
 1274          Rect l4 = new Rect(fieldbase) { x = fieldbase.xMin + 3 * (fieldwidth + SPACING) };
 1275          GUI.Label(l1, "L:", _compactLabelStyle);
 1276          GUI.Label(l2, "R:", _compactLabelStyle);
 1277          GUI.Label(l3, "T:", _compactLabelStyle);
 1278          GUI.Label(l4, "B:", _compactLabelStyle);
 1279        }
 1280
 1281        // Draw value fields
 1282        Rect f1 = new Rect(fieldbase) { x = fieldbase.xMin + 0 * fieldwidth + (useCompact ? 0 : LABELS_WIDTH) };
 1283        Rect f2 = new Rect(fieldbase) { x = fieldbase.xMin + 1 * fieldwidth + (useCompact ? 0 : LABELS_WIDTH) + 1 * SPAC
 1284        Rect f3 = new Rect(fieldbase) { x = fieldbase.xMin + 2 * fieldwidth + (useCompact ? 0 : LABELS_WIDTH) + 2 * SPAC
 1285        Rect f4 = new Rect(fieldbase) { x = fieldbase.xMin + 3 * fieldwidth + (useCompact ? 0 : LABELS_WIDTH) + 3 * SPAC
 1286
 1287        using (var check = new EditorGUI.ChangeCheckScope()) {
 1288          float newxmin, newxmax, newymin, newymax;
 1289          if (invertY) {
 1290            newxmin = EditorGUI.DelayedFloatField(f1, (float)Math.Round(value.xMin, useCompact ? 2 : 3), valueStyle);
 1291            newxmax = EditorGUI.DelayedFloatField(f2, (float)Math.Round(value.xMax, useCompact ? 2 : 3), valueStyle);
 1292            newymax = EditorGUI.DelayedFloatField(f3, (float)Math.Round(value.yMax, useCompact ? 2 : 3), valueStyle);
 1293            newymin = EditorGUI.DelayedFloatField(f4, (float)Math.Round(value.yMin, useCompact ? 2 : 3), valueStyle);
 1294          } else {
 1295            newxmin = EditorGUI.DelayedFloatField(f1, (float)Math.Round(value.xMin, useCompact ? 2 : 3), valueStyle);
 1296            newxmax = EditorGUI.DelayedFloatField(f2, (float)Math.Round(value.xMax, useCompact ? 2 : 3), valueStyle);
 1297            newymin = EditorGUI.DelayedFloatField(f3, (float)Math.Round(value.yMin, useCompact ? 2 : 3), valueStyle);
 1298            newymax = EditorGUI.DelayedFloatField(f4, (float)Math.Round(value.yMax, useCompact ? 2 : 3), valueStyle);
 1299          }
 1300
 1301          if (check.changed) {
 1302            if (newxmin != value.xMin) value.xMin = Math.Min(newxmin, value.xMax - .05f);
 1303            if (newxmax != value.xMax) value.xMax = Math.Max(newxmax, value.xMin + .05f);
 1304            if (newymax != value.yMax) value.yMax = Math.Max(newymax, value.yMin + .05f);
 1305            if (newymin != value.yMin) value.yMin = Math.Min(newymin, value.yMax - .05f);
 1306            property.rectValue = value;
 1307            property.serializedObject.ApplyModifiedProperties();
 1308          }
 1309        }
 1310
 1311        var nmins = new Vector2(value.xMin, invertY ? 1f - value.yMin : value.yMin);
 1312        var nmaxs = new Vector2(value.xMax, invertY ? 1f - value.yMax : value.yMax);
 1313        var mins = Rect.NormalizedToPoint(dragarea, nmins);
 1314        var maxs = Rect.NormalizedToPoint(dragarea, nmaxs);
 1315        var area = Rect.MinMaxRect(minreal.x, invertY ? maxreal.y : minreal.y, maxreal.x, invertY ? minreal.y : maxreal.
 1316
 1317        EditorGUI.DrawRect(area, new Color(1f, 1f, 1f, .1f));
 1318        //GUI.DrawTexture(area, GUIContent.none, EditorStyles.helpBox);
 1319        //GUI.Box(area, GUIContent.none, EditorStyles.helpBox);
 1320
 1321      } else {
 1322        Debug.LogWarning($"{nameof(NormalizedRectAttribute)} only valid on UnityEngine.Rect fields. Will use default ren
 1323        EditorGUI.PropertyField(position, property, label);
 1324      }
 1325
 1326      if (hasChanged) {
 1327        GUI.changed = true;
 1328        property.serializedObject.ApplyModifiedProperties();
 1329       }
 1330
 1331      EditorGUI.EndProperty();
 1332    }
 1333  }
 1334#endif
 1335
 1336}
 1337
 1338
 1339#endregion
 1340
 1341
 1342#region Assets/Photon/Fusion/Editor/CustomTypes/SceneRefDrawer.cs
 1343
 1344namespace Fusion.Editor {
 1345  using System;
 1346  using System.Collections.Generic;
 1347  using System.Reflection;
 1348  using UnityEditor;
 1349  using UnityEngine;
 1350
 1351  [CustomPropertyDrawer(typeof(SceneRef))]
 1352  public class SceneRefDrawer : PropertyDrawer {
 1353
 1354    public const int CheckboxWidth = 16;
 1355
 1356    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
 1357
 1358      using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out position)) {
 1359        var valueProperty = property.FindPropertyRelativeOrThrow(nameof(SceneRef.RawValue));
 1360        long rawValue = valueProperty.longValue;
 1361
 1362        var togglePos = position;
 1363        togglePos.width = CheckboxWidth;
 1364        bool hasValue = rawValue > 0;
 1365
 1366        EditorGUI.BeginChangeCheck();
 1367
 1368        if (EditorGUI.Toggle(togglePos, hasValue) != hasValue) {
 1369          rawValue = valueProperty.longValue = hasValue ? 0 : 1;
 1370          valueProperty.serializedObject.ApplyModifiedProperties();
 1371        }
 1372
 1373        if (rawValue > 0) {
 1374          position.xMin += togglePos.width;
 1375
 1376          rawValue = EditorGUI.LongField(position, rawValue - 1);
 1377          rawValue = Math.Max(0, rawValue) + 1;
 1378
 1379          if (EditorGUI.EndChangeCheck()) {
 1380            valueProperty.longValue = rawValue;
 1381            valueProperty.serializedObject.ApplyModifiedProperties();
 1382          }
 1383        }
 1384      }
 1385    }
 1386  }
 1387}
 1388
 1389#endregion
 1390
 1391
 1392#region Assets/Photon/Fusion/Editor/CustomTypes/SerializableDictionaryDrawer.cs
 1393
 1394namespace Fusion.Editor {
 1395  using System;
 1396  using System.Collections.Generic;
 1397  using System.Linq;
 1398  using System.Reflection;
 1399  using System.Text;
 1400  using System.Threading.Tasks;
 1401  using UnityEditor;
 1402  using UnityEditorInternal;
 1403  using UnityEngine;
 1404
 1405  [CustomPropertyDrawer(typeof(SerializableDictionary), true)]
 1406  class SerializableDictionaryDrawer : PropertyDrawerWithErrorHandling {
 1407    const string ItemsPropertyPath    = SerializableDictionary<int,int>.ItemsPropertyPath;
 1408    const string EntryKeyPropertyPath = SerializableDictionary<int, int>.EntryKeyPropertyPath;
 1409
 1410    protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 1411      var entries = property.FindPropertyRelativeOrThrow(ItemsPropertyPath);
 1412      entries.isExpanded = property.isExpanded;
 1413      using (new FusionEditorGUI.PropertyScope(position, label, property)) {
 1414        EditorGUI.PropertyField(position, entries, label, true);
 1415        property.isExpanded = entries.isExpanded;
 1416
 1417        string error = VerifyDictionary(entries, EntryKeyPropertyPath);
 1418        if (error != null) {
 1419          SetError(error);
 1420        } else {
 1421          ClearError();
 1422        }
 1423      }
 1424    }
 1425    public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
 1426      var entries = property.FindPropertyRelativeOrThrow(ItemsPropertyPath);
 1427      return EditorGUI.GetPropertyHeight(entries, label, true);
 1428    }
 1429
 1430    private static HashSet<SerializedProperty> _dictionaryKeyHash = new HashSet<SerializedProperty>(new SerializedProper
 1431
 1432    private static string VerifyDictionary(SerializedProperty prop, string keyPropertyName) {
 1433      Debug.Assert(prop.isArray);
 1434      try {
 1435        for (int i = 0; i < prop.arraySize; ++i) {
 1436          var keyProperty = prop.GetArrayElementAtIndex(i).FindPropertyRelativeOrThrow(keyPropertyName);
 1437          if (!_dictionaryKeyHash.Add(keyProperty)) {
 1438
 1439            var groups = Enumerable.Range(0, prop.arraySize)
 1440                .GroupBy(x => prop.GetArrayElementAtIndex(x).FindPropertyRelative(keyPropertyName), x => x, _dictionaryK
 1441                .Where(x => x.Count() > 1)
 1442                .ToList();
 1443
 1444            // there are duplicates - take the slow and allocating path now
 1445            return string.Join("\n", groups.Select(x => $"Duplicate keys for elements: {string.Join(", ", x)}"));
 1446          }
 1447        }
 1448
 1449        return null;
 1450
 1451      } finally {
 1452        _dictionaryKeyHash.Clear();
 1453      }
 1454    }
 1455  }
 1456}
 1457
 1458
 1459#endregion
 1460
 1461
 1462#region Assets/Photon/Fusion/Editor/CustomTypes/TickRateDrawer.cs
 1463
 1464namespace Fusion.Editor {
 1465  using System;
 1466  using System.Reflection;
 1467  using UnityEditor;
 1468  using UnityEngine;
 1469
 1470  [CustomPropertyDrawer(typeof(TickRate.Selection))]
 1471  [FusionPropertyDrawerMeta(HasFoldout = false)]
 1472  public class TickRateDrawer : PropertyDrawer {
 1473    private const int PAD = 0;
 1474
 1475    // Cached pop items for client rate
 1476    private static GUIContent[] _clientRateOptions;
 1477    private static GUIContent[] ClientRateOptions {
 1478      get {
 1479        if (_clientRateOptions != null) {
 1480          return _clientRateOptions;
 1481        }
 1482        ExtractClientRates();
 1483        return _clientRateOptions;
 1484      }
 1485    }
 1486
 1487    // Cached pop items for client rate
 1488    private static int[] _clientRateValues;
 1489    private static int[] ClientRateValues {
 1490      get {
 1491        if (_clientRateValues != null) {
 1492          return _clientRateValues;
 1493        }
 1494        ExtractClientRates();
 1495        return _clientRateValues;
 1496      }
 1497    }
 1498    //
 1499    // private static GUIContent[] _ratioOptions = new GUIContent[4];
 1500    // private static int[] _ratioValues  = new int[] { 0, 1, 2, 3 };
 1501
 1502    private static readonly GUIContent[][] _reusableRatioGUIArrays = new GUIContent[4][] { new GUIContent[1], new GUICon
 1503    private static readonly int[][]        _reusableRatioIntArrays = new int[4][] { new int[1], new int[2], new int[3], 
 1504
 1505    private static readonly GUIContent[][] _reusableServerGUIArrays = new GUIContent[4][] { new GUIContent[1], new GUICo
 1506    private static readonly int[][]        _reusableServerIntArrays = new int[4][] { new int[1], new int[2], new int[3],
 1507
 1508    private static readonly LazyGUIStyle _buttonStyle = LazyGUIStyle.Create(_ => new GUIStyle(EditorStyles.miniButton) {
 1509      fontSize  = 9,
 1510      alignment = TextAnchor.MiddleCenter
 1511    });
 1512
 1513    public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
 1514      return (base.GetPropertyHeight(property, label) + EditorGUIUtility.standardVerticalSpacing) * 4 + PAD * 2;
 1515    }
 1516
 1517    private int DrawPopup(ref Rect labelRect, ref Rect fieldRect, float rowHeight, GUIContent guiContent, int[] sliderVa
 1518
 1519      EditorGUI.LabelField(labelRect, guiContent);
 1520      int indentHold = EditorGUI.indentLevel;
 1521      EditorGUI.indentLevel = 0;
 1522      int  value;
 1523      Rect dropRect = fieldRect;
 1524
 1525      if (fieldRect.width > 120) {
 1526        var slideRect   = new Rect(fieldRect) { xMax = fieldRect.xMax - 64 };
 1527        dropRect = new Rect(fieldRect) { xMin = fieldRect.xMax - 64 };
 1528
 1529        var sliderRange = Math.Max(3, sliderValues.Length             - 1);
 1530        if (sliderRange == 3) {
 1531          var dividerRect = new Rect(slideRect);
 1532          // dividerRect.yMin += 2;
 1533          // dividerRect.yMax -= 2;
 1534          var quarter     = slideRect.width * 1f /4;
 1535
 1536          using (new EditorGUI.DisabledScope(!(options.Length + offset >= 4))) {
 1537            if (GUI.Toggle(new Rect(dividerRect) { width = quarter }, currentValue == 3, new GUIContent("1/8"), _buttonS
 1538               currentValue = 3;
 1539            }
 1540          }
 1541          using (new EditorGUI.DisabledScope(!(options.Length + offset >= 3 && offset <= 2))) {
 1542            if (GUI.Toggle(new Rect(dividerRect) { width = quarter, x = dividerRect.x + quarter }, currentValue == 2, ne
 1543               currentValue = 2;
 1544            }
 1545          }
 1546          using (new EditorGUI.DisabledScope(!(options.Length + offset >= 2 && offset <= 1))) {
 1547            if (GUI.Toggle(new Rect(dividerRect) { width = quarter, x = dividerRect.x + quarter * 2 }, currentValue == 1
 1548               currentValue = 1;
 1549            }
 1550          }
 1551          using (new EditorGUI.DisabledScope(!(options.Length + offset >= 1 && offset == 0))) {
 1552            if (GUI.Toggle(new Rect(dividerRect) { width = quarter, x = dividerRect.x + quarter * 3 }, currentValue == 0
 1553              currentValue = 0;
 1554            }
 1555          }
 1556          EditorGUI.LabelField(dropRect, options[currentValue - offset], new GUIStyle(EditorStyles.label){padding = new 
 1557          value = values[currentValue - offset];
 1558
 1559        } else {
 1560          currentValue = (int)GUI.HorizontalSlider(slideRect, (float)currentValue, sliderRange, 0);
 1561
 1562          // Clamp slider ranges into valid enum ranges
 1563          if (currentValue - offset < 0) {
 1564            currentValue = offset;
 1565          }
 1566          else if (currentValue - offset >= values.Length) {
 1567            currentValue = values.Length - 1 + offset;
 1568          }
 1569          value = values[EditorGUI.Popup(dropRect, GUIContent.none, currentValue - offset, options)];
 1570          // value = values[EditorGUI.Popup(fieldRect, GUIContent.none, currentValue - offset, options)];
 1571
 1572        }
 1573
 1574      } else {
 1575        // Handling for very narrow window. Falls back to just a basic popup for each value.
 1576        dropRect = fieldRect;
 1577        var index = EditorGUI.Popup(dropRect, GUIContent.none, currentValue - offset, options);
 1578        index = Math.Clamp(index, 0, values.Length-1);
 1579        value = values[index];
 1580      }
 1581
 1582      EditorGUI.indentLevel = indentHold;
 1583      labelRect.y += rowHeight + EditorGUIUtility.standardVerticalSpacing;
 1584      fieldRect.y += rowHeight + EditorGUIUtility.standardVerticalSpacing;
 1585
 1586      return value;
 1587    }
 1588
 1589    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
 1590
 1591      using (new FusionEditorGUI.PropertyScope(position, label, property)) {
 1592
 1593        var rowHeight = base.GetPropertyHeight(property, label);
 1594
 1595        // using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out position)) {
 1596        // EditorGUI.LabelField(new Rect(position){ yMin = position.yMin + rowHeight}, GUIContent.none, FusionGUIStyles.
 1597
 1598        position = new Rect(position) {
 1599          xMin = position.xMin + PAD,
 1600          xMax = position.xMax - PAD,
 1601          yMin = position.yMin + PAD,
 1602          yMax = position.yMax - PAD
 1603        };
 1604
 1605        var clientRateProperty = property.FindPropertyRelativeOrThrow(nameof(TickRate.Selection.Client));
 1606        var serverRateProperty = property.FindPropertyRelativeOrThrow(nameof(TickRate.Selection.ServerIndex));
 1607        var clientSendProperty = property.FindPropertyRelativeOrThrow(nameof(TickRate.Selection.ClientSendIndex));
 1608        var serverSendProperty = property.FindPropertyRelativeOrThrow(nameof(TickRate.Selection.ServerSendIndex));
 1609
 1610        var selection = GetSelectionValue(property);
 1611
 1612        var hold            = selection;
 1613        var tickRate        = TickRate.Get(TickRate.IsValid(selection.Client) ? selection.Client : TickRate.Default.Clie
 1614        var clientRateIndex = GetIndexForClientRate(tickRate.Client);
 1615
 1616        var rect = new Rect(position) { height = base.GetPropertyHeight(property, label) };
 1617
 1618        //var fieldWidth =  Math.Max(Math.Min(position.width * .33f, MAX_FIELD_WIDTH), MIN_FIELD_WIDTH);
 1619        var labelWidth = EditorGUIUtility.labelWidth;
 1620
 1621
 1622        var labelRect = new Rect(rect) { width = labelWidth}; // { xMax = rect.xMax - fieldWidth }};
 1623        //var fieldRect = new Rect(rect) { xMin  = rect.xMax -fieldWidth };
 1624        var fieldRect = new Rect(rect) { xMin = rect.xMin  + labelWidth};
 1625
 1626        // CLIENT SIM RATE
 1627
 1628        selection.Client = DrawPopup(ref labelRect, ref fieldRect, rowHeight, new GUIContent("Client Tick Rate"), _clien
 1629
 1630        // TODO: This validates every tick without checking for changes. May be good, may not.
 1631        selection = tickRate.ClampSelection(selection);
 1632
 1633        // CLIENT SEND RATE
 1634        var ratioOptions = _reusableRatioGUIArrays[tickRate.Count - 1]; // _ratioOptions;
 1635        var ratioValues  = _reusableRatioIntArrays[tickRate.Count - 1]; //_ratioValues;
 1636        for (var i = 0; i < tickRate.Count; ++i) {
 1637          ratioOptions[i] = new GUIContent(tickRate.GetTickRate(i).ToString());
 1638          ratioValues[i]  = i;
 1639        }
 1640
 1641        selection.ClientSendIndex = DrawPopup(ref labelRect, ref fieldRect, rowHeight, new GUIContent("Client Send Rate"
 1642
 1643        // SERVER SIM RATE - Force it to be 1:1 with the client tick rate - since different tick rates are not supported
 1644        var srOptions = _reusableServerGUIArrays[0];
 1645        var srValues = _reusableServerIntArrays[0];
 1646
 1647        srOptions[0] = ratioOptions[0];
 1648        srValues[0] = 0;
 1649
 1650        selection.ServerIndex = DrawPopup(ref labelRect, ref fieldRect, rowHeight, new GUIContent("Server Tick Rate"), r
 1651
 1652        selection = tickRate.ClampSelection(selection);
 1653
 1654        // SERVER SEND RATE - uses a subset of ratio - since it CANNOT be higher than Server Rate.
 1655        var sOffset      = selection.ServerIndex;
 1656        var sLen         = ratioOptions.Length - sOffset;
 1657        var sSendOptions = _reusableServerGUIArrays[sLen - 1]; // new GUIContent[sLen];
 1658        var sSendValues  = _reusableServerIntArrays[sLen - 1]; // new int[sLen];
 1659
 1660        for (var i = 0; i < sLen; ++i) {
 1661          sSendOptions[i] = ratioOptions[i + sOffset];
 1662          sSendValues[i]  = ratioValues[i  + sOffset];
 1663        }
 1664
 1665        selection.ServerSendIndex = DrawPopup(ref labelRect, ref fieldRect, rowHeight, new GUIContent("Server Send Rate"
 1666
 1667        if (hold.Equals(selection) == false) {
 1668          selection = tickRate.ClampSelection(selection);
 1669
 1670          // FIELD INFO SET VALUE ALTERNATIVE
 1671          // fieldInfo.SetValue(targetObject, selection);
 1672
 1673          clientRateProperty.intValue = selection.Client;
 1674          clientSendProperty.intValue = selection.ClientSendIndex;
 1675          serverRateProperty.intValue = selection.ServerIndex;
 1676          serverSendProperty.intValue = selection.ServerSendIndex;
 1677          property.serializedObject.ApplyModifiedProperties();
 1678        }
 1679      }
 1680    }
 1681
 1682    private int GetIndexForClientRate(int clientRate) {
 1683      for (var i = ClientRateValues.Length - 1; i >= 0; --i)
 1684        if (_clientRateValues[i] == clientRate) {
 1685          return i;
 1686        }
 1687      return -1;
 1688    }
 1689
 1690
 1691    // Extract in reverse order so all the popups are consistent.
 1692    private static void ExtractClientRates() {
 1693      int cnt = TickRate.Available.Count;
 1694
 1695      _clientRateOptions = new GUIContent[cnt];
 1696      _clientRateValues  = new int[cnt];
 1697      for (int i = 0, reverse = cnt -1; i < cnt; ++i, --reverse) {
 1698        _clientRateOptions[i] = new GUIContent(TickRate.Available[reverse].Client.ToString());
 1699        _clientRateValues[i]  = TickRate.Available[reverse].Client;
 1700      }
 1701    }
 1702
 1703    // Wacky reflection to locate the value
 1704    private static TickRate.Selection GetSelectionValue (SerializedProperty property) {
 1705      object   obj   = property.serializedObject.targetObject;
 1706      string   path  = property.propertyPath;
 1707      string[] parts = path.Split ('.');
 1708      foreach (var t in parts) {
 1709        obj = GetValueFromFieldName (t, obj);
 1710      }
 1711      return (TickRate.Selection)obj;
 1712    }
 1713
 1714    private static object GetValueFromFieldName(string name, object obj, BindingFlags bindings = BindingFlags.Instance |
 1715      FieldInfo field = obj.GetType().GetField(name, bindings);
 1716      if (field != null) {
 1717        return field.GetValue(obj);
 1718      }
 1719
 1720      return TickRate.Default;
 1721    }
 1722
 1723  }
 1724}
 1725
 1726#endregion
 1727
 1728
 1729#region Assets/Photon/Fusion/Editor/EditorRecompileHook.cs
 1730
 1731namespace Fusion.Editor {
 1732  using System;
 1733  using System.IO;
 1734  using UnityEditor;
 1735  using UnityEditor.Compilation;
 1736  using UnityEngine;
 1737
 1738  [InitializeOnLoad]
 1739  public static class EditorRecompileHook {
 1740    static EditorRecompileHook() {
 1741
 1742      EditorApplication.update += delegate {
 1743        if (PlayerSettings.allowUnsafeCode == false) {
 1744          PlayerSettings.allowUnsafeCode = true;
 1745
 1746          // request re-compile
 1747          CompilationPipeline.RequestScriptCompilation(RequestScriptCompilationOptions.None);
 1748        }
 1749      };
 1750
 1751      AssemblyReloadEvents.beforeAssemblyReload += ShutdownRunners;
 1752
 1753      CompilationPipeline.compilationStarted    += _ => ShutdownRunners();
 1754      CompilationPipeline.compilationStarted    += _ => StoreConfigPath();
 1755    }
 1756
 1757    static void ShutdownRunners() {
 1758      var runners = NetworkRunner.GetInstancesEnumerator();
 1759
 1760      while (runners.MoveNext()) {
 1761        if (runners.Current) {
 1762          runners.Current.Shutdown();
 1763        }
 1764      }
 1765    }
 1766
 1767    static void StoreConfigPath() {
 1768      const string ConfigPathCachePath = "Temp/FusionILWeaverConfigPath.txt";
 1769
 1770      var configPath = NetworkProjectConfigUtilities.GetGlobalConfigPath();
 1771      if (string.IsNullOrEmpty(configPath)) {
 1772        // delete
 1773        try {
 1774          File.Delete(ConfigPathCachePath);
 1775        } catch (FileNotFoundException) {
 1776          // ok
 1777        } catch (Exception ex) {
 1778          FusionEditorLog.ErrorConfig($"Error when clearing the config path file for the Weaver. Weaving results may be 
 1779        }
 1780      } else {
 1781        try {
 1782          System.IO.File.WriteAllText(ConfigPathCachePath, configPath);
 1783        } catch (Exception ex) {
 1784          FusionEditorLog.ErrorConfig($"Error when writing the config path file for the Weaver. Weaving results may be i
 1785        }
 1786      }
 1787    }
 1788  }
 1789}
 1790
 1791#endregion
 1792
 1793
 1794#region Assets/Photon/Fusion/Editor/FusionAssistants.cs
 1795
 1796namespace Fusion.Editor {
 1797  using UnityEngine;
 1798  using System;
 1799
 1800  static class FusionAssistants {
 1801    public const int PRIORITY = 0;
 1802    public const int PRIORITY_LOW = 1000;
 1803
 1804    /// <summary>
 1805    /// Ensure GameObject has component T. Will create as needed and return the found/created component.
 1806    /// </summary>
 1807    public static T EnsureComponentExists<T>(this GameObject go) where T : Component {
 1808      if (go.TryGetComponent<T>(out var t))
 1809        return t;
 1810
 1811      else
 1812        return go.AddComponent<T>();
 1813    }
 1814
 1815    public static GameObject EnsureComponentsExistInScene(string preferredGameObjectName, params Type[] components) {
 1816
 1817      GameObject go = null;
 1818
 1819      foreach(var c in components) {
 1820        var found = UnityEngine.Object.FindFirstObjectByType(c);
 1821        if (found)
 1822          continue;
 1823
 1824        if (go == null)
 1825          go = new GameObject(preferredGameObjectName);
 1826
 1827        go.AddComponent(c);
 1828      }
 1829
 1830      return go;
 1831    }
 1832
 1833    public static T EnsureExistsInScene<T>(string preferredGameObjectName = null, GameObject onThisObject = null, params
 1834
 1835      if (preferredGameObjectName == null)
 1836        preferredGameObjectName = typeof(T).Name;
 1837
 1838      T comp;
 1839      comp = UnityEngine.Object.FindFirstObjectByType<T>();
 1840      if (comp == null) {
 1841        // T was not found in scene, create a new gameobject and add T, as well as other required components
 1842        if (onThisObject == null)
 1843          onThisObject = new GameObject(preferredGameObjectName);
 1844        comp = onThisObject.AddComponent<T>();
 1845        foreach (var add in otherRequiredComponents) {
 1846          onThisObject.AddComponent(add);
 1847        }
 1848      } else {
 1849        // Make sure existing found T has the indicated extra components as well.
 1850        foreach (var add in otherRequiredComponents) {
 1851          if (comp.GetComponent(add) == false)
 1852            comp.gameObject.AddComponent(add);
 1853        }
 1854      }
 1855      return comp;
 1856    }
 1857
 1858    /// <summary>
 1859    /// Create a scene object with all of the supplied arguments and parameters applied.
 1860    /// </summary>
 1861    public static GameObject CreatePrimitive(
 1862      PrimitiveType? primitive,
 1863      string name,
 1864      Vector3? position,
 1865      Quaternion? rotation,
 1866      Vector3? scale,
 1867      Transform parent,
 1868      Material material,
 1869      params Type[] addComponents) {
 1870
 1871      GameObject go;
 1872      if (primitive.HasValue) {
 1873        go = GameObject.CreatePrimitive(primitive.Value);
 1874
 1875        go.name = name;
 1876
 1877        if (material != null)
 1878          go.GetComponent<Renderer>().material = material;
 1879
 1880        foreach (var type in addComponents) {
 1881          go.AddComponent(type);
 1882        }
 1883
 1884      } else {
 1885        go = new GameObject(name, addComponents);
 1886      }
 1887
 1888      if (position.HasValue)
 1889        go.transform.position = position.Value;
 1890
 1891      if (rotation.HasValue)
 1892        go.transform.rotation = rotation.Value;
 1893
 1894      if (scale.HasValue)
 1895        go.transform.localScale = scale.Value;
 1896
 1897      if (parent)
 1898        go.transform.parent = parent;
 1899
 1900      return go;
 1901    }
 1902
 1903    internal static EnableOnSingleRunner EnsureComponentHasVisibilityNode(this Component component) {
 1904      var allExistingNodes = component.GetComponents<EnableOnSingleRunner>();
 1905      foreach (var existingNodes in allExistingNodes) {
 1906        foreach (var comp in existingNodes.Components) {
 1907          if (comp == component) {
 1908            return existingNodes;
 1909          }
 1910        }
 1911      }
 1912
 1913      // Component is not represented yet. If there is a VisNodes already, use it. Otherwise make one.
 1914      EnableOnSingleRunner targetNodes = component.GetComponent<EnableOnSingleRunner>();
 1915      if (targetNodes == null) {
 1916        targetNodes = component.gameObject.AddComponent<EnableOnSingleRunner>();
 1917      }
 1918
 1919      // Add this component to the collection.
 1920      int newArrayPos = targetNodes.Components.Length;
 1921      Array.Resize(ref targetNodes.Components, newArrayPos + 1);
 1922      targetNodes.Components[newArrayPos] = component;
 1923      return targetNodes;
 1924    }
 1925  }
 1926}
 1927
 1928#endregion
 1929
 1930
 1931#region Assets/Photon/Fusion/Editor/FusionBootstrapEditor.cs
 1932
 1933namespace Fusion.Editor {
 1934  using System.Linq;
 1935  using UnityEditor;
 1936  using UnityEngine;
 1937  using UnityEngine.SceneManagement;
 1938
 1939  [CustomEditor(typeof(FusionBootstrap))]
 1940  public class FusionBootstrapEditor : BehaviourEditor {
 1941
 1942    public override void OnInspectorGUI() {
 1943      base.OnInspectorGUI();
 1944
 1945      if (Application.isPlaying)
 1946        return;
 1947
 1948      var currentScene = SceneManager.GetActiveScene();
 1949      if (!currentScene.IsAddedToBuildSettings()) {
 1950        using (new FusionEditorGUI.WarningScope("Current scene is not added to Build Settings list.")) {
 1951          if (GUILayout.Button("Add Scene To Build Settings")) {
 1952            if (currentScene.name == "") {
 1953              UnityEditor.SceneManagement.EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo();
 1954            }
 1955
 1956            if (currentScene.name != "") {
 1957              EditorBuildSettings.scenes = EditorBuildSettings.scenes
 1958               .Concat(new[] { new EditorBuildSettingsScene(currentScene.path, true) })
 1959               .ToArray();
 1960            }
 1961          }
 1962        }
 1963      }
 1964    }
 1965  }
 1966}
 1967
 1968#endregion
 1969
 1970
 1971#region Assets/Photon/Fusion/Editor/FusionBuildTriggers.cs
 1972
 1973namespace Fusion.Editor {
 1974
 1975  using UnityEditor;
 1976  using UnityEditor.Build;
 1977  using UnityEditor.Build.Reporting;
 1978
 1979
 1980  public class FusionBuildTriggers : IPreprocessBuildWithReport {
 1981
 1982    public const int CallbackOrder = 1000;
 1983
 1984    public int callbackOrder => CallbackOrder;
 1985
 1986    public void OnPreprocessBuild(BuildReport report) {
 1987      if (report.summary.platformGroup != BuildTargetGroup.Standalone) {
 1988        return;
 1989      }
 1990
 1991      if (!PlayerSettings.runInBackground) {
 1992        FusionEditorLog.Warn($"Standalone builds should have {nameof(PlayerSettings)}.{nameof(PlayerSettings.runInBackgr
 1993          $"Otherwise, loss of application focus may result in connection termination.");
 1994      }
 1995    }
 1996  }
 1997}
 1998
 1999
 2000#endregion
 2001
 2002
 2003#region Assets/Photon/Fusion/Editor/FusionEditor.Common.cs
 2004
 2005// merged Editor
 2006
 2007#region INetworkAssetSourceFactory.cs
 2008
 2009namespace Fusion.Editor {
 2010  using UnityEditor;
 2011
 2012  /// <summary>
 2013  /// A factory that creates <see cref="INetworkAssetSource"/> instances for a given asset.
 2014  /// </summary>
 2015  public partial interface INetworkAssetSourceFactory {
 2016    /// <summary>
 2017    /// The order in which this factory is executed. The lower the number, the earlier it is executed.
 2018    /// </summary>
 2019    int Order { get; }
 2020  }
 2021
 2022  /// <summary>
 2023  /// A context object that is passed to <see cref="INetworkAssetSourceFactory"/> instances to create an <see cref="INet
 2024  /// </summary>
 2025  public readonly partial struct NetworkAssetSourceFactoryContext {
 2026    /// <summary>
 2027    /// Asset instance ID.
 2028    /// </summary>
 2029    public readonly int    InstanceID;
 2030    /// <summary>
 2031    /// Asset Unity GUID;
 2032    /// </summary>
 2033    public readonly string AssetGuid;
 2034    /// <summary>
 2035    /// Asset name;
 2036    /// </summary>
 2037    public readonly string AssetName;
 2038    /// <summary>
 2039    /// Is this the main asset.
 2040    /// </summary>
 2041    public readonly bool   IsMainAsset;
 2042    /// <summary>
 2043    /// Asset Unity path.
 2044    /// </summary>
 2045    public string AssetPath => AssetDatabaseUtils.GetAssetPathOrThrow(InstanceID);
 2046
 2047    /// <summary>
 2048    /// Create a new instance of <see cref="NetworkAssetSourceFactoryContext"/>.
 2049    /// </summary>
 2050    public NetworkAssetSourceFactoryContext(string assetGuid, int instanceID, string assetName, bool isMainAsset) {
 2051      AssetGuid = assetGuid;
 2052      InstanceID = instanceID;
 2053      AssetName = assetName;
 2054      IsMainAsset = isMainAsset;
 2055    }
 2056
 2057    /// <summary>
 2058    /// Create a new instance of <see cref="NetworkAssetSourceFactoryContext"/>.
 2059    /// </summary>
 2060    public NetworkAssetSourceFactoryContext(HierarchyProperty hierarchyProperty) {
 2061      AssetGuid = hierarchyProperty.guid;
 2062      InstanceID = hierarchyProperty.instanceID;
 2063      AssetName = hierarchyProperty.name;
 2064      IsMainAsset = hierarchyProperty.isMainRepresentation;
 2065    }
 2066
 2067    /// <summary>
 2068    /// Create a new instance of <see cref="NetworkAssetSourceFactoryContext"/>.
 2069    /// </summary>
 2070    public NetworkAssetSourceFactoryContext(UnityEngine.Object obj) {
 2071      if (!obj) {
 2072        throw new System.ArgumentNullException(nameof(obj));
 2073      }
 2074
 2075      var instanceId = obj.GetInstanceID();
 2076      (AssetGuid, _) = AssetDatabaseUtils.GetGUIDAndLocalFileIdentifierOrThrow(instanceId);
 2077      InstanceID = instanceId;
 2078      AssetName = obj.name;
 2079      IsMainAsset = AssetDatabase.IsMainAsset(instanceId);
 2080    }
 2081  }
 2082}
 2083
 2084#endregion
 2085
 2086
 2087#region NetworkAssetSourceFactoryAddressable.cs
 2088
 2089#if (FUSION_ADDRESSABLES || FUSION_ENABLE_ADDRESSABLES) && !FUSION_DISABLE_ADDRESSABLES
 2090namespace Fusion.Editor {
 2091  using UnityEditor.AddressableAssets;
 2092
 2093  /// <summary>
 2094  /// A <see cref="INetworkAssetSourceFactory"/> implementation that creates <see cref="NetworkAssetSourceAddressable{TA
 2095  /// if the asset is an Addressable.
 2096  /// </summary>
 2097  public partial class NetworkAssetSourceFactoryAddressable : INetworkAssetSourceFactory {
 2098    /// <inheritdoc cref="INetworkAssetSourceFactory.Order"/>
 2099    public const int Order = 800;
 2100
 2101    int INetworkAssetSourceFactory.Order => Order;
 2102
 2103    /// <summary>
 2104    /// Creates a new instance. Checks if AddressableAssetSettings exists and logs a warning if it does not.
 2105    /// </summary>
 2106    public NetworkAssetSourceFactoryAddressable() {
 2107      if (!AddressableAssetSettingsDefaultObject.SettingsExists) {
 2108        FusionEditorLog.WarnImport($"AddressableAssetSettings does not exist, Fusion will not be able to use Addressable
 2109      }
 2110    }
 2111
 2112    /// <summary>
 2113    /// Creates <see cref="NetworkAssetSourceAddressable{TAsset}"/> if the asset is an Addressable.
 2114    /// </summary>
 2115    protected bool TryCreateInternal<TSource, TAsset>(in NetworkAssetSourceFactoryContext context, out TSource result)
 2116      where TSource : NetworkAssetSourceAddressable<TAsset>, new()
 2117      where TAsset : UnityEngine.Object {
 2118
 2119      if (!AddressableAssetSettingsDefaultObject.SettingsExists) {
 2120        result = default;
 2121        return false;
 2122      }
 2123
 2124      var assetsSettings = AddressableAssetSettingsDefaultObject.Settings;
 2125      if (assetsSettings == null) {
 2126        throw new System.InvalidOperationException("Unable to load Addressables settings. This may be due to an outdated
 2127      }
 2128
 2129      var addressableEntry = assetsSettings.FindAssetEntry(context.AssetGuid, true);
 2130      if (addressableEntry == null) {
 2131        result = default;
 2132        return false;
 2133      }
 2134
 2135      result = new TSource() {
 2136        RuntimeKey = $"{addressableEntry.guid}{(context.IsMainAsset ? string.Empty : $"[{context.AssetName}]")}",
 2137      };
 2138      return true;
 2139    }
 2140  }
 2141}
 2142#endif
 2143
 2144#endregion
 2145
 2146
 2147#region NetworkAssetSourceFactoryResource.cs
 2148
 2149namespace Fusion.Editor {
 2150  /// <summary>
 2151  /// A <see cref="INetworkAssetSourceFactory"/> implementation that creates <see cref="NetworkAssetSourceResource{TAsse
 2152  /// instances for assets in the Resources folder.
 2153  /// </summary>
 2154  public partial class NetworkAssetSourceFactoryResource : INetworkAssetSourceFactory {
 2155    /// <inheritdoc cref="INetworkAssetSourceFactory.Order"/>
 2156    public const int Order = 1000;
 2157
 2158    int INetworkAssetSourceFactory.Order => Order;
 2159
 2160    /// <summary>
 2161    /// Creates <see cref="NetworkAssetSourceResource{T}"/> if the asset is in the Resources folder.
 2162    /// </summary>
 2163    protected bool TryCreateInternal<TSource, TAsset>(in NetworkAssetSourceFactoryContext context, out TSource result)
 2164      where TSource : NetworkAssetSourceResource<TAsset>, new()
 2165      where TAsset : UnityEngine.Object {
 2166      if (!PathUtils.TryMakeRelativeToFolder(context.AssetPath, "/Resources/", out var resourcePath)) {
 2167        result = default;
 2168        return false;
 2169      }
 2170
 2171      var withoutExtension = PathUtils.GetPathWithoutExtension(resourcePath);
 2172      result = new TSource() {
 2173        ResourcePath = withoutExtension,
 2174        SubObjectName = context.IsMainAsset ? string.Empty : context.AssetName,
 2175      };
 2176      return true;
 2177    }
 2178  }
 2179}
 2180
 2181#endregion
 2182
 2183
 2184#region NetworkAssetSourceFactoryStatic.cs
 2185
 2186namespace Fusion.Editor {
 2187  using UnityEditor;
 2188  using UnityEngine;
 2189
 2190  /// <summary>
 2191  /// A <see cref="INetworkAssetSourceFactory"/> implementation that creates <see cref="NetworkAssetSourceStaticLazy{TAs
 2192  /// </summary>
 2193  public partial  class NetworkAssetSourceFactoryStatic : INetworkAssetSourceFactory {
 2194    /// <inheritdoc cref="INetworkAssetSourceFactory.Order"/>
 2195    public const int Order = int.MaxValue;
 2196
 2197    int INetworkAssetSourceFactory.Order => Order;
 2198
 2199    /// <summary>
 2200    /// Creates <see cref="NetworkAssetSourceStaticLazy{TAsset}"/>.
 2201    /// </summary>
 2202    protected bool TryCreateInternal<TSource, TAsset>(in NetworkAssetSourceFactoryContext context, out TSource result)
 2203      where TSource : NetworkAssetSourceStaticLazy<TAsset>, new()
 2204      where TAsset : UnityEngine.Object {
 2205
 2206      if (typeof(TAsset).IsSubclassOf(typeof(Component))) {
 2207        var prefab = (GameObject)EditorUtility.InstanceIDToObject(context.InstanceID);
 2208
 2209        result = new TSource() {
 2210          Object = prefab.GetComponent<TAsset>()
 2211        };
 2212
 2213      } else {
 2214        result = new TSource() {
 2215          Object = new(context.InstanceID)
 2216        };
 2217      }
 2218      return true;
 2219    }
 2220  }
 2221}
 2222
 2223#endregion
 2224
 2225
 2226#region AssetDatabaseUtils.Addressables.cs
 2227
 2228#if (FUSION_ADDRESSABLES || FUSION_ENABLE_ADDRESSABLES) && !FUSION_DISABLE_ADDRESSABLES
 2229namespace Fusion.Editor {
 2230  using System;
 2231  using System.Collections.Generic;
 2232  using System.Linq;
 2233  using UnityEditor;
 2234  using UnityEditor.AddressableAssets;
 2235  using UnityEditor.AddressableAssets.Settings;
 2236  using UnityEngine;
 2237
 2238  partial class AssetDatabaseUtils {
 2239    /// <summary>
 2240    /// Register a handler that will be called when an addressable asset with a specific label is added or removed.
 2241    /// </summary>
 2242    public static void AddAddressableAssetsWithLabelMonitor(string label, Action<Hash128> handler) {
 2243      AddressableAssetSettings.OnModificationGlobal += (settings, modificationEvent, data) => {
 2244        switch (modificationEvent) {
 2245          case AddressableAssetSettings.ModificationEvent.EntryAdded:
 2246          case AddressableAssetSettings.ModificationEvent.EntryCreated:
 2247          case AddressableAssetSettings.ModificationEvent.EntryModified:
 2248          case AddressableAssetSettings.ModificationEvent.EntryMoved:
 2249
 2250            IEnumerable<AddressableAssetEntry> entries;
 2251            if (data is AddressableAssetEntry singleEntry) {
 2252              entries = Enumerable.Repeat(singleEntry, 1);
 2253            } else {
 2254              entries = (IEnumerable<AddressableAssetEntry>)data;
 2255            }
 2256
 2257            List<AddressableAssetEntry> allEntries = new List<AddressableAssetEntry>();
 2258            foreach (var entry in entries) {
 2259              entry.GatherAllAssets(allEntries, true, true, true);
 2260              if (allEntries.Any(x => HasLabel(x.AssetPath, label))) {
 2261                handler(settings.currentHash);
 2262                break;
 2263              }
 2264
 2265              allEntries.Clear();
 2266            }
 2267
 2268            break;
 2269
 2270          case AddressableAssetSettings.ModificationEvent.EntryRemoved:
 2271            // TODO: check what has been removed
 2272            handler(settings.currentHash);
 2273            break;
 2274        }
 2275      };
 2276    }
 2277
 2278    internal static AddressableAssetEntry GetAddressableAssetEntry(UnityEngine.Object source) {
 2279      if (source == null || !AssetDatabase.Contains(source)) {
 2280        return null;
 2281      }
 2282
 2283      return GetAddressableAssetEntry(GetAssetGuidOrThrow(source));
 2284    }
 2285
 2286    internal static AddressableAssetEntry GetAddressableAssetEntry(string guid) {
 2287      if (string.IsNullOrEmpty(guid)) {
 2288        return null;
 2289      }
 2290
 2291      var addressableSettings = AddressableAssetSettingsDefaultObject.Settings;
 2292      return addressableSettings.FindAssetEntry(guid);
 2293    }
 2294
 2295    internal static AddressableAssetEntry CreateOrMoveAddressableAssetEntry(UnityEngine.Object source, string groupName 
 2296      if (source == null || !AssetDatabase.Contains(source))
 2297        return null;
 2298
 2299      return CreateOrMoveAddressableAssetEntry(GetAssetGuidOrThrow(source), groupName);
 2300    }
 2301
 2302    internal static AddressableAssetEntry CreateOrMoveAddressableAssetEntry(string guid, string groupName = null) {
 2303      if (string.IsNullOrEmpty(guid)) {
 2304        return null;
 2305      }
 2306
 2307      var addressableSettings = AddressableAssetSettingsDefaultObject.Settings;
 2308
 2309      AddressableAssetGroup group;
 2310      if (string.IsNullOrEmpty(groupName)) {
 2311        group = addressableSettings.DefaultGroup;
 2312      } else {
 2313        group = addressableSettings.FindGroup(groupName);
 2314      }
 2315
 2316      if (group == null) {
 2317        throw new ArgumentOutOfRangeException($"Group {groupName} not found");
 2318      }
 2319
 2320      var entry = addressableSettings.CreateOrMoveEntry(guid, group);
 2321      return entry;
 2322    }
 2323
 2324    internal static bool RemoveMoveAddressableAssetEntry(UnityEngine.Object source) {
 2325      if (source == null || !AssetDatabase.Contains(source)) {
 2326        return false;
 2327      }
 2328
 2329      return RemoveMoveAddressableAssetEntry(GetAssetGuidOrThrow(source));
 2330    }
 2331
 2332    internal static bool RemoveMoveAddressableAssetEntry(string guid) {
 2333      if (string.IsNullOrEmpty(guid)) {
 2334        return false;
 2335      }
 2336
 2337      var addressableSettings = AddressableAssetSettingsDefaultObject.Settings;
 2338      return addressableSettings.RemoveAssetEntry(guid);
 2339    }
 2340
 2341    [InitializeOnLoadMethod]
 2342    static void InitializeRuntimeCallbacks() {
 2343      FusionAddressablesUtils.SetLoadEditorInstanceHandler(LoadEditorInstance);
 2344    }
 2345
 2346    private static UnityEngine.Object LoadEditorInstance(string runtimeKey) {
 2347      if (string.IsNullOrEmpty(runtimeKey)) {
 2348        return default;
 2349      }
 2350
 2351      if (!FusionAddressablesUtils.TryParseAddress(runtimeKey, out var mainKey, out var subKey)) {
 2352        throw new ArgumentException($"Invalid address: {runtimeKey}", nameof(runtimeKey));
 2353      }
 2354
 2355      if (GUID.TryParse(mainKey, out _)) {
 2356        // a guid one, we can load it
 2357        if (string.IsNullOrEmpty(subKey)) {
 2358          var asset = AssetDatabase.LoadMainAssetAtPath(AssetDatabase.GUIDToAssetPath(mainKey));
 2359          if (asset != null) {
 2360            return asset;
 2361          }
 2362        } else {
 2363          foreach (var subAsset in AssetDatabase.LoadAllAssetRepresentationsAtPath(AssetDatabase.GUIDToAssetPath(mainKey
 2364            if (subAsset.name == subKey) {
 2365              return subAsset;
 2366            }
 2367          }
 2368
 2369          // not returning null here, as there might be a chance for a guid-like address
 2370        }
 2371      }
 2372
 2373      // need to resort to addressable asset settings
 2374      // path... this sucks
 2375      if (!AddressableAssetSettingsDefaultObject.SettingsExists) {
 2376        FusionEditorLog.Error($"Unable to load asset: {runtimeKey}; AddressableAssetSettings does not exist");
 2377        return default;
 2378      }
 2379
 2380      var settings = AddressableAssetSettingsDefaultObject.Settings;
 2381      Assert.Check(settings != null);
 2382
 2383      var list = new List<AddressableAssetEntry>();
 2384      settings.GetAllAssets(list, true, entryFilter: x => {
 2385        if (x.IsFolder) {
 2386          return mainKey.StartsWith(x.address, StringComparison.OrdinalIgnoreCase);
 2387        } else {
 2388          return mainKey.Equals(x.address, StringComparison.OrdinalIgnoreCase);
 2389        }
 2390      });
 2391
 2392      // given the filtering above, the list will contain more than one if we
 2393      // check for a root asset that has nested assets
 2394      foreach (var entry in list) {
 2395        if (runtimeKey.Equals(entry.address, StringComparison.OrdinalIgnoreCase)) {
 2396          return entry.TargetAsset;
 2397        }
 2398      }
 2399
 2400      return default;
 2401    }
 2402  }
 2403}
 2404#endif
 2405
 2406#endregion
 2407
 2408
 2409#region AssetDatabaseUtils.cs
 2410
 2411namespace Fusion.Editor {
 2412  using System;
 2413  using System.Collections;
 2414  using System.Collections.Generic;
 2415  using System.Linq;
 2416  using UnityEditor;
 2417  using UnityEditor.Build;
 2418  using UnityEditor.PackageManager;
 2419  using UnityEngine;
 2420
 2421  /// <summary>
 2422  /// Utility methods for working with Unity's <see cref="AssetDatabase"/>
 2423  /// </summary>
 2424  public static partial class AssetDatabaseUtils {
 2425
 2426    /// <summary>
 2427    /// Sets the asset dirty and, if is a sub-asset, also sets the main asset dirty.
 2428    /// </summary>
 2429    /// <param name="obj"></param>
 2430    public static void SetAssetAndTheMainAssetDirty(UnityEngine.Object obj) {
 2431      EditorUtility.SetDirty(obj);
 2432
 2433      var assetPath = AssetDatabase.GetAssetPath(obj);
 2434      if (string.IsNullOrEmpty(assetPath)) {
 2435        return;
 2436      }
 2437      var mainAsset = AssetDatabase.LoadMainAssetAtPath(assetPath);
 2438      if (!mainAsset || mainAsset == obj) {
 2439        return;
 2440      }
 2441      EditorUtility.SetDirty(mainAsset);
 2442    }
 2443
 2444    /// <summary>
 2445    /// Returns the asset path for the given instance ID or throws an exception if the asset is not found.
 2446    /// </summary>
 2447    public static string GetAssetPathOrThrow(int instanceID) {
 2448      var result = AssetDatabase.GetAssetPath(instanceID);
 2449      if (string.IsNullOrEmpty(result)) {
 2450        throw new ArgumentException($"Asset with InstanceID {instanceID} not found");
 2451      }
 2452      return result;
 2453    }
 2454
 2455    /// <summary>
 2456    /// Returns the asset path for the given object or throws an exception if <paramref name="obj"/> is
 2457    /// not an asset.
 2458    /// </summary>
 2459    public static string GetAssetPathOrThrow(UnityEngine.Object obj) {
 2460      var result = AssetDatabase.GetAssetPath(obj);
 2461      if (string.IsNullOrEmpty(result)) {
 2462        throw new ArgumentException($"Asset {obj} not found");
 2463      }
 2464      return result;
 2465    }
 2466
 2467    /// <summary>
 2468    /// Returns the asset path for the given asset GUID or throws an exception if the asset is not found.
 2469    /// </summary>
 2470    public static string GetAssetPathOrThrow(string assetGuid) {
 2471      var result = AssetDatabase.GUIDToAssetPath(assetGuid);
 2472      if (string.IsNullOrEmpty(result)) {
 2473        throw new ArgumentException($"Asset with Guid {assetGuid} not found");
 2474      }
 2475
 2476      return result;
 2477    }
 2478
 2479    /// <summary>
 2480    /// Returns the asset GUID for the given asset path or throws an exception if the asset is not found.
 2481    /// </summary>
 2482    public static string GetAssetGuidOrThrow(string assetPath) {
 2483      var result = AssetDatabase.AssetPathToGUID(assetPath);
 2484      if (string.IsNullOrEmpty(result)) {
 2485        throw new ArgumentException($"Asset with path {assetPath} not found");
 2486      }
 2487
 2488      return result;
 2489    }
 2490
 2491    /// <summary>
 2492    /// Returns the asset GUID for the given instance ID or throws an exception if the asset is not found.
 2493    /// </summary>
 2494    public static string GetAssetGuidOrThrow(int instanceId) {
 2495      var assetPath = GetAssetPathOrThrow(instanceId);
 2496      return GetAssetGuidOrThrow(assetPath);
 2497    }
 2498
 2499    /// <summary>
 2500    /// Returns the asset GUID for the given object reference or throws an exception if the asset is not found.
 2501    /// </summary>
 2502    public static string GetAssetGuidOrThrow(UnityEngine.Object obj) {
 2503      var assetPath = GetAssetPathOrThrow(obj);
 2504      return GetAssetGuidOrThrow(assetPath);
 2505    }
 2506
 2507    /// <summary>
 2508    /// Gets the GUID and local file identifier for the given object reference or throws an exception if the asset is no
 2509    /// </summary>
 2510    public static (string, long) GetGUIDAndLocalFileIdentifierOrThrow<T>(LazyLoadReference<T> reference) where T : Unity
 2511      if (!AssetDatabase.TryGetGUIDAndLocalFileIdentifier(reference, out var guid, out long localId)) {
 2512        throw new ArgumentException($"Asset with instanceId {reference} not found");
 2513      }
 2514
 2515      return (guid, localId);
 2516    }
 2517
 2518    /// <summary>
 2519    /// Gets the GUID and local file identifier for the given object reference or throws an exception if the asset is no
 2520    /// </summary>
 2521    public static (string, long) GetGUIDAndLocalFileIdentifierOrThrow(UnityEngine.Object obj) {
 2522      if (!AssetDatabase.TryGetGUIDAndLocalFileIdentifier(obj, out var guid, out long localId)) {
 2523        throw new ArgumentException(nameof(obj));
 2524      }
 2525
 2526      return (guid, localId);
 2527    }
 2528
 2529    /// <summary>
 2530    /// Gets the GUID and local file identifier for the instance ID or throws an exception if the asset is not found.
 2531    /// </summary>
 2532    public static (string, long) GetGUIDAndLocalFileIdentifierOrThrow(int instanceId) {
 2533      if (!AssetDatabase.TryGetGUIDAndLocalFileIdentifier(instanceId, out var guid, out long localId)) {
 2534        throw new ArgumentException($"Asset with instanceId {instanceId} not found");
 2535      }
 2536
 2537      return (guid, localId);
 2538    }
 2539
 2540    /// <summary>
 2541    /// Moves the asset at <paramref name="source"/> to <paramref name="destination"/> or throws an exception if the mov
 2542    /// </summary>
 2543    public static void MoveAssetOrThrow(string source, string destination) {
 2544      var error = AssetDatabase.MoveAsset(source, destination);
 2545      if (!string.IsNullOrEmpty(error)) {
 2546        throw new ArgumentException($"Failed to move {source} to {destination}: {error}");
 2547      }
 2548    }
 2549
 2550    /// <summary>
 2551    /// Returns <see langword="true"/> if the asset at <paramref name="assetPath"/> has the given <paramref name="label"
 2552    /// </summary>
 2553    public static bool HasLabel(string assetPath, string label) {
 2554      var guidStr = AssetDatabase.AssetPathToGUID(assetPath);
 2555      if (!GUID.TryParse(guidStr, out var guid)) {
 2556        return false;
 2557      }
 2558
 2559      var labels = AssetDatabase.GetLabels(guid);
 2560      var index  = Array.IndexOf(labels, label);
 2561      return index >= 0;
 2562    }
 2563
 2564    /// <summary>
 2565    /// Returns <see langword="true"/> if the asset <paramref name="obj"/> has the given <paramref name="label"/>.
 2566    /// </summary>
 2567    public static bool HasLabel(UnityEngine.Object obj, string label) {
 2568      var labels = AssetDatabase.GetLabels(obj);
 2569      var index  = Array.IndexOf(labels, label);
 2570      return index >= 0;
 2571    }
 2572
 2573    /// <summary>
 2574    /// Returns <see langword="true"/> if the asset <paramref name="guid"/> has the given <paramref name="label"/>.
 2575    /// </summary>
 2576    public static bool HasLabel(GUID guid, string label) {
 2577      var labels = AssetDatabase.GetLabels(guid);
 2578      var index  = Array.IndexOf(labels, label);
 2579      return index >= 0;
 2580    }
 2581
 2582    /// <summary>
 2583    /// Returns <see langword="true"/> if the asset at <paramref name="assetPath"/> has any of the given <paramref name=
 2584    /// </summary>
 2585    public static bool HasAnyLabel(string assetPath, params string[] labels) {
 2586      var guidStr = AssetDatabase.AssetPathToGUID(assetPath);
 2587      if (!GUID.TryParse(guidStr, out var guid)) {
 2588        return false;
 2589      }
 2590
 2591      var assetLabels = AssetDatabase.GetLabels(guid);
 2592      foreach (var label in labels) {
 2593        if (Array.IndexOf(assetLabels, label) >= 0) {
 2594          return true;
 2595        }
 2596      }
 2597
 2598      return false;
 2599    }
 2600
 2601    /// <summary>
 2602    /// Sets or unsets <paramref name="label"/> label for the asset at <paramref name="assetPath"/>, depending
 2603    /// on the value of <paramref name="present"/>.
 2604    /// </summary>
 2605    /// <returns><see langword="true"/> if there was a change to the labels.</returns>
 2606    public static bool SetLabel(string assetPath, string label, bool present) {
 2607      var guid = AssetDatabase.GUIDFromAssetPath(assetPath);
 2608      if (guid.Empty()) {
 2609        return false;
 2610      }
 2611
 2612      var labels = AssetDatabase.GetLabels(guid);
 2613      var index  = Array.IndexOf(labels, label);
 2614      if (present) {
 2615        if (index >= 0) {
 2616          return false;
 2617        }
 2618        ArrayUtility.Add(ref labels, label);
 2619      } else {
 2620        if (index < 0) {
 2621          return false;
 2622        }
 2623        ArrayUtility.RemoveAt(ref labels, index);
 2624      }
 2625
 2626      var obj = AssetDatabase.LoadMainAssetAtPath(assetPath);
 2627      if (obj == null) {
 2628        return false;
 2629      }
 2630
 2631      AssetDatabase.SetLabels(obj, labels);
 2632      return true;
 2633    }
 2634
 2635    /// <summary>
 2636    /// Sets or unsets the <paramref name="label"/> label for the asset <paramref name="obj"/>, depending
 2637    /// on the value of <paramref name="present"/>.
 2638    /// </summary>
 2639    /// <returns><see langword="true"/> if there was a change to the labels.</returns>
 2640    public static bool SetLabel(UnityEngine.Object obj, string label, bool present) {
 2641      var labels = AssetDatabase.GetLabels(obj);
 2642      var index  = Array.IndexOf(labels, label);
 2643      if (present) {
 2644        if (index >= 0) {
 2645          return false;
 2646        }
 2647        ArrayUtility.Add(ref labels, label);
 2648      } else {
 2649        if (index < 0) {
 2650          return false;
 2651        }
 2652        ArrayUtility.RemoveAt(ref labels, index);
 2653      }
 2654
 2655      AssetDatabase.SetLabels(obj, labels);
 2656      return true;
 2657    }
 2658
 2659    /// <summary>
 2660    /// Sets all the labels for the asset at <paramref name="assetPath"/>.
 2661    /// </summary>
 2662    /// <returns><see langword="true"/> if the asset was found</returns>
 2663    public static bool SetLabels(string assetPath, string[] labels) {
 2664      var obj = AssetDatabase.LoadMainAssetAtPath(assetPath);
 2665      if (obj == null) {
 2666        return false;
 2667      }
 2668
 2669      AssetDatabase.SetLabels(obj, labels);
 2670      return true;
 2671    }
 2672
 2673    /// <summary>
 2674    /// Checks if a scripting define <paramref name="value"/> is defined for <paramref name="group"/>.
 2675    /// </summary>
 2676    public static bool HasScriptingDefineSymbol(BuildTargetGroup group, string value) {
 2677      var defines = PlayerSettings.GetScriptingDefineSymbols(NamedBuildTarget.FromBuildTargetGroup(group)).Split(';');
 2678      return System.Array.IndexOf(defines, value) >= 0;
 2679    }
 2680
 2681    /// <inheritdoc cref="SetScriptableObjectType"/>
 2682    public static T SetScriptableObjectType<T>(ScriptableObject obj) where T : ScriptableObject {
 2683      return (T)SetScriptableObjectType(obj, typeof(T));
 2684    }
 2685
 2686    /// <summary>
 2687    /// Changes the type of scriptable object.
 2688    /// </summary>
 2689    /// <returns>The new instance with requested type</returns>
 2690    public static ScriptableObject SetScriptableObjectType(ScriptableObject obj, Type type) {
 2691      const string ScriptPropertyName = "m_Script";
 2692
 2693      if (!obj) {
 2694        throw new ArgumentNullException(nameof(obj));
 2695      }
 2696      if (type == null) {
 2697        throw new ArgumentNullException(nameof(type));
 2698      }
 2699      if (!type.IsSubclassOf(typeof(ScriptableObject))) {
 2700        throw new ArgumentException($"Type {type} is not a subclass of {nameof(ScriptableObject)}");
 2701      }
 2702
 2703      if (obj.GetType() == type) {
 2704        return obj;
 2705      }
 2706
 2707      var tmp = ScriptableObject.CreateInstance(type);
 2708      try {
 2709        using (var dst = new SerializedObject(obj)) {
 2710          using (var src = new SerializedObject(tmp)) {
 2711            var scriptDst = dst.FindPropertyOrThrow(ScriptPropertyName);
 2712            var scriptSrc = src.FindPropertyOrThrow(ScriptPropertyName);
 2713            Debug.Assert(scriptDst.objectReferenceValue != scriptSrc.objectReferenceValue);
 2714            dst.CopyFromSerializedProperty(scriptSrc);
 2715            dst.ApplyModifiedPropertiesWithoutUndo();
 2716            return (ScriptableObject)dst.targetObject;
 2717          }
 2718        }
 2719      } finally {
 2720        UnityEngine.Object.DestroyImmediate(tmp);
 2721      }
 2722    }
 2723
 2724    private static bool IsEnumValueObsolete<T>(string valueName) where T : System.Enum {
 2725      var fi         = typeof(T).GetField(valueName);
 2726      var attributes = fi.GetCustomAttributes(typeof(System.ObsoleteAttribute), false);
 2727      return attributes?.Length > 0;
 2728    }
 2729
 2730    internal static IEnumerable<BuildTargetGroup> ValidBuildTargetGroups {
 2731      get {
 2732        foreach (var name in System.Enum.GetNames(typeof(BuildTargetGroup))) {
 2733          if (IsEnumValueObsolete<BuildTargetGroup>(name))
 2734            continue;
 2735          var group = (BuildTargetGroup)System.Enum.Parse(typeof(BuildTargetGroup), name);
 2736          if (group == BuildTargetGroup.Unknown)
 2737            continue;
 2738
 2739          yield return group;
 2740        }
 2741      }
 2742    }
 2743
 2744    /// <summary>
 2745    /// Checks if any and all <see cref="BuildTargetGroup"/> have the given scripting define symbol.
 2746    /// </summary>
 2747    /// <returns><see langword="true"/> if all groups have the symbol, <see langword="false"/> if none have it, <see lan
 2748    public static bool? HasScriptingDefineSymbol(string value) {
 2749      bool anyDefined = false;
 2750      bool anyUndefined = false;
 2751      foreach (BuildTargetGroup group in ValidBuildTargetGroups) {
 2752        if (HasScriptingDefineSymbol(group, value)) {
 2753          anyDefined = true;
 2754        } else {
 2755          anyUndefined = true;
 2756        }
 2757      }
 2758
 2759      return (anyDefined && anyUndefined) ? (bool?)null : anyDefined;
 2760    }
 2761
 2762    /// <summary>
 2763    /// Adds or removes <paramref name="define"/> scripting define symbol from <paramref name="group"/>, depending
 2764    /// on the value of <paramref name="enable"/>
 2765    /// </summary>
 2766    public static void UpdateScriptingDefineSymbol(BuildTargetGroup group, string define, bool enable) {
 2767      UpdateScriptingDefineSymbolInternal(new[] { group },
 2768        enable ? new[] { define } : null,
 2769        enable ? null : new[] { define });
 2770    }
 2771
 2772    /// <summary>
 2773    /// Adds or removes <paramref name="define"/> from all <see cref="BuildTargetGroup"/>s, depending on the value of <p
 2774    /// </summary>
 2775    public static void UpdateScriptingDefineSymbol(string define, bool enable) {
 2776      UpdateScriptingDefineSymbolInternal(ValidBuildTargetGroups,
 2777        enable ? new[] { define } : null,
 2778        enable ? null : new[] { define });
 2779    }
 2780
 2781    internal static void UpdateScriptingDefineSymbol(BuildTargetGroup group, IEnumerable<string> definesToAdd, IEnumerab
 2782      UpdateScriptingDefineSymbolInternal(new[] { group },
 2783        definesToAdd,
 2784        definesToRemove);
 2785    }
 2786
 2787    internal static void UpdateScriptingDefineSymbol(IEnumerable<string> definesToAdd, IEnumerable<string> definesToRemo
 2788      UpdateScriptingDefineSymbolInternal(ValidBuildTargetGroups,
 2789        definesToAdd,
 2790        definesToRemove);
 2791    }
 2792
 2793    private static void UpdateScriptingDefineSymbolInternal(IEnumerable<BuildTargetGroup> groups, IEnumerable<string> de
 2794      EditorApplication.LockReloadAssemblies();
 2795      try {
 2796        foreach (var group in groups) {
 2797          var originalDefines = PlayerSettings.GetScriptingDefineSymbols(NamedBuildTarget.FromBuildTargetGroup(group));
 2798          var defines = originalDefines.Split(';').ToList();
 2799
 2800          if (definesToRemove != null) {
 2801            foreach (var d in definesToRemove) {
 2802              defines.Remove(d);
 2803            }
 2804          }
 2805
 2806          if (definesToAdd != null) {
 2807            foreach (var d in definesToAdd) {
 2808              defines.Remove(d);
 2809              defines.Add(d);
 2810            }
 2811          }
 2812
 2813          var newDefines = string.Join(";", defines);
 2814          PlayerSettings.SetScriptingDefineSymbols(NamedBuildTarget.FromBuildTargetGroup(group), newDefines);
 2815        }
 2816      } finally {
 2817        EditorApplication.UnlockReloadAssemblies();
 2818      }
 2819    }
 2820
 2821    /// <summary>
 2822    /// Iterates over all assets in the project that match the given search criteria, without
 2823    /// actually loading them.
 2824    /// </summary>
 2825    /// <param name="root">The optional root folder</param>
 2826    /// <param name="label">The optional label</param>
 2827    public static AssetEnumerable IterateAssets<T>(string root = null, string label = null) where T : UnityEngine.Object
 2828      return IterateAssets(root, label, typeof(T));
 2829    }
 2830
 2831    /// <summary>
 2832    /// Iterates over all assets in the project that match the given search criteria, without
 2833    /// actually loading them.
 2834    /// </summary>
 2835    /// <param name="root">The optional root folder</param>
 2836    /// <param name="label">The optional label</param>
 2837    /// <param name="type">The optional type</param>
 2838    public static AssetEnumerable IterateAssets(string root = null, string label = null, Type type = null) {
 2839      return new AssetEnumerable(root, label, type);
 2840    }
 2841
 2842    static Lazy<string[]> s_rootFolders = new Lazy<string[]>(() => new[] { "Assets" }.Concat(UnityEditor.PackageManager.
 2843      .Where(x => !IsPackageHidden(x))
 2844      .Select(x => x.assetPath))
 2845      .ToArray());
 2846
 2847    private static bool IsPackageHidden(UnityEditor.PackageManager.PackageInfo info) => info.type == "module" || info.ty
 2848
 2849    /// <summary>
 2850    /// Enumerates assets in the project that match the given search criteria using <see cref="HierarchyProperty"/> API.
 2851    /// Obtained with <see cref="AssetDatabaseUtils.IterateAssets"/>.
 2852    /// </summary>
 2853    public struct AssetEnumerator : IEnumerator<HierarchyProperty> {
 2854
 2855      private HierarchyProperty _hierarchyProperty;
 2856      private int               _rootFolderIndex;
 2857
 2858      private readonly string[] _rootFolders;
 2859
 2860      /// <summary>
 2861      /// Creates a new instance.
 2862      /// </summary>
 2863      public AssetEnumerator(string root, string label, Type type) {
 2864        var searchFilter = MakeSearchFilter(label, type);
 2865        _rootFolderIndex = 0;
 2866        if (string.IsNullOrEmpty(root)) {
 2867          // search everywhere
 2868          _rootFolders = s_rootFolders.Value;
 2869          _hierarchyProperty = new HierarchyProperty(_rootFolders[0]);
 2870        } else {
 2871          _rootFolders       = null;
 2872          _hierarchyProperty = new HierarchyProperty(root);
 2873        }
 2874
 2875        _hierarchyProperty.SetSearchFilter(searchFilter, (int)SearchableEditorWindow.SearchMode.All);
 2876      }
 2877
 2878      /// <summary>
 2879      /// Updates internal <see cref="HierarchyProperty"/>.
 2880      /// </summary>
 2881      /// <returns></returns>
 2882      public bool MoveNext() {
 2883        if (_hierarchyProperty.Next(null)) {
 2884          return true;
 2885        }
 2886
 2887        if (_rootFolders == null || _rootFolderIndex + 1 >= _rootFolders.Length) {
 2888          return false;
 2889        }
 2890
 2891        var newHierarchyProperty = new HierarchyProperty(_rootFolders[++_rootFolderIndex]);
 2892        UnityInternal.HierarchyProperty.CopySearchFilterFrom(newHierarchyProperty, _hierarchyProperty);
 2893        _hierarchyProperty = newHierarchyProperty;
 2894
 2895        // try again
 2896        return MoveNext();
 2897      }
 2898
 2899      /// <summary>
 2900      /// Throws <see cref="System.NotImplementedException"/>.
 2901      /// </summary>
 2902      /// <exception cref="NotImplementedException"></exception>
 2903      public void Reset() {
 2904        throw new System.NotImplementedException();
 2905      }
 2906
 2907      /// <summary>
 2908      /// Returns the internernal <see cref="HierarchyProperty"/>. Most of the time
 2909      /// this will be the same instance as returned the last time, so do not cache
 2910      /// the result - check its properties intestead.
 2911      /// </summary>
 2912      public HierarchyProperty Current => _hierarchyProperty;
 2913
 2914      object IEnumerator.Current => Current;
 2915
 2916      /// <inheritdoc/>
 2917      public void Dispose() {
 2918      }
 2919
 2920      private static string MakeSearchFilter(string label, Type type) {
 2921        string searchFilter;
 2922        if (type == typeof(GameObject)) {
 2923          searchFilter = "t:prefab";
 2924        } else if (type != null) {
 2925          searchFilter = "t:" + type.FullName;
 2926        } else {
 2927          searchFilter = "";
 2928        }
 2929
 2930        if (!string.IsNullOrEmpty(label)) {
 2931          if (searchFilter.Length > 0) {
 2932            searchFilter += " ";
 2933          }
 2934
 2935          searchFilter += "l:" + label;
 2936        }
 2937
 2938        return searchFilter;
 2939      }
 2940    }
 2941
 2942    /// <summary>
 2943    /// Enumerable of assets in the project that match the given search criteria.
 2944    /// </summary>
 2945    /// <seealso cref="AssetEnumerator"/>
 2946    public struct AssetEnumerable : IEnumerable<HierarchyProperty> {
 2947
 2948      private readonly string _root;
 2949      private readonly string _label;
 2950      private readonly Type   _type;
 2951
 2952      /// <summary>
 2953      /// Not intended to be called directly. Use <see cref="AssetDatabaseUtils.IterateAssets"/> instead.
 2954      /// </summary>
 2955      public AssetEnumerable(string root, string label, Type type) {
 2956        _type  = type;
 2957        _root  = root;
 2958        _label = label;
 2959      }
 2960
 2961      /// <summary>
 2962      /// Not intended to be called directly. Use <see cref="AssetDatabaseUtils.IterateAssets"/> instead.
 2963      /// </summary>
 2964      public AssetEnumerator GetEnumerator() => new AssetEnumerator(_root, _label, _type);
 2965
 2966      IEnumerator<HierarchyProperty> IEnumerable<HierarchyProperty>.GetEnumerator() => GetEnumerator();
 2967
 2968      IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
 2969    }
 2970
 2971    /// <summary>
 2972    /// Sends out <see cref="FusionMppmRegisterCustomDependencyCommand"/> command to virtual peers
 2973    /// before calling <see cref="AssetDatabase.RegisterCustomDependency"/>.
 2974    /// </summary>
 2975    public static void RegisterCustomDependencyWithMppmWorkaround(string customDependency, Hash128 hash) {
 2976      FusionMppm.MainEditor?.Send(new FusionMppmRegisterCustomDependencyCommand() {
 2977        DependencyName = customDependency,
 2978        Hash = hash.ToString(),
 2979      });
 2980      AssetDatabase.RegisterCustomDependency(customDependency, hash);
 2981    }
 2982  }
 2983}
 2984
 2985#endregion
 2986
 2987
 2988#region EditorButtonDrawer.cs
 2989
 2990namespace Fusion.Editor {
 2991  using System;
 2992  using System.Collections.Generic;
 2993  using System.Linq;
 2994  using System.Reflection;
 2995  using UnityEditor;
 2996  using UnityEngine;
 2997
 2998  struct EditorButtonDrawer {
 2999
 3000    private struct ButtonEntry {
 3001      public MethodInfo                                Method;
 3002      public GUIContent                                Content;
 3003      public EditorButtonAttribute                     Attribute;
 3004      public (DoIfAttributeBase, Func<object, object>)[] DoIfs;
 3005    }
 3006
 3007    private Editor            _lastEditor;
 3008    private List<ButtonEntry> _buttons;
 3009
 3010    public void Draw(Editor editor) {
 3011      var targets    = editor.targets;
 3012
 3013      if (_lastEditor != editor) {
 3014        _lastEditor = editor;
 3015        Refresh(editor);
 3016      }
 3017
 3018      if (_buttons == null || targets == null || targets.Length == 0) {
 3019        return;
 3020      }
 3021
 3022      foreach (var entry in _buttons) {
 3023
 3024        if (entry.Attribute.Visibility == EditorButtonVisibility.PlayMode && !EditorApplication.isPlaying) {
 3025          continue;
 3026        }
 3027
 3028        if (entry.Attribute.Visibility == EditorButtonVisibility.EditMode && EditorApplication.isPlaying) {
 3029          continue;
 3030        }
 3031
 3032        if (!entry.Attribute.AllowMultipleTargets && editor.targets.Length > 1) {
 3033          continue;
 3034        }
 3035
 3036        bool   readOnly       = false;
 3037        bool   hidden         = false;
 3038        string warningMessage = null;
 3039        bool warningAsBox = false;
 3040
 3041        foreach (var (doIf, getter) in entry.DoIfs) {
 3042
 3043          bool checkResult;
 3044
 3045          if (getter == null) {
 3046            checkResult = DoIfAttributeDrawer.CheckDraw(doIf, editor.serializedObject);
 3047          } else {
 3048            var value = getter(targets[0]);
 3049            checkResult = DoIfAttributeDrawer.CheckCondition(doIf, value);
 3050          }
 3051
 3052          if (!checkResult) {
 3053            if (doIf is DrawIfAttribute drawIf) {
 3054              if (drawIf.Hide) {
 3055                hidden = true;
 3056                break;
 3057              } else {
 3058                readOnly = true;
 3059              }
 3060            } else if (doIf is WarnIfAttribute warnIf) {
 3061              warningMessage = warnIf.Message;
 3062              warningAsBox   = warnIf.AsBox;
 3063            }
 3064          }
 3065        }
 3066
 3067        if (hidden) {
 3068          continue;
 3069        }
 3070
 3071        using (warningMessage == null ? null : (IDisposable)new FusionEditorGUI.WarningScope(warningMessage)) {
 3072          var rect = FusionEditorGUI.LayoutHelpPrefix(editor, entry.Method);
 3073          using (new EditorGUI.DisabledScope(readOnly)) {
 3074            if (GUI.Button(rect, entry.Content)) {
 3075              EditorGUI.BeginChangeCheck();
 3076
 3077              if (entry.Method.IsStatic) {
 3078                entry.Method.Invoke(null, null);
 3079              } else {
 3080                foreach (var target in targets) {
 3081                  entry.Method.Invoke(target, null);
 3082                  if (entry.Attribute.DirtyObject) {
 3083                    EditorUtility.SetDirty(target);
 3084                  }
 3085                }
 3086              }
 3087
 3088              if (EditorGUI.EndChangeCheck()) {
 3089                editor.serializedObject.Update();
 3090              }
 3091            }
 3092          }
 3093        }
 3094      }
 3095    }
 3096
 3097    private void Refresh(Editor editor) {
 3098      if (editor == null) {
 3099        throw new ArgumentNullException(nameof(editor));
 3100      }
 3101
 3102      var targetType = editor.target.GetType();
 3103
 3104      _buttons = targetType
 3105       .GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingF
 3106       .Where(x => x.GetParameters().Length == 0 && x.IsDefined(typeof(EditorButtonAttribute)))
 3107       .Select(method => {
 3108          var attribute = method.GetCustomAttribute<EditorButtonAttribute>();
 3109          var label     = new GUIContent(attribute.Label ?? ObjectNames.NicifyVariableName(method.Name));
 3110          var drawIfs = method.GetCustomAttributes<DoIfAttributeBase>()
 3111           .Select(x => {
 3112              var prop = editor.serializedObject.FindProperty(x.ConditionMember);
 3113              return prop != null ? (x, null) : (x, targetType.CreateGetter(x.ConditionMember));
 3114            })
 3115             .ToArray();
 3116          return new ButtonEntry() {
 3117            Attribute = attribute,
 3118            Content   = label,
 3119            Method    = method,
 3120            DoIfs     = drawIfs,
 3121          };
 3122        })
 3123       .OrderBy(x => x.Attribute.Priority)
 3124       .ToList();
 3125    }
 3126  }
 3127}
 3128
 3129#endregion
 3130
 3131
 3132#region EnumDrawer.cs
 3133
 3134namespace Fusion.Editor {
 3135  using System;
 3136  using System.Collections.Generic;
 3137  using System.Linq;
 3138  using System.Reflection;
 3139  using UnityEditor;
 3140  using UnityEngine;
 3141
 3142  struct EnumDrawer {
 3143    private Mask256[]   _values;
 3144    private string[]    _names;
 3145    private bool        _isFlags;
 3146    private Type        _enumType;
 3147    private Mask256     _allBitMask;
 3148    private FieldInfo[] _fields;
 3149
 3150    [NonSerialized]
 3151    private List<int> _selectedIndices;
 3152
 3153    public Mask256[]   Values   => _values;
 3154    public string[]    Names    => _names;
 3155    public bool        IsFlags  => _isFlags;
 3156    public Type        EnumType => _enumType;
 3157    public Mask256     BitMask  => _allBitMask;
 3158    public FieldInfo[] Fields   => _fields;
 3159
 3160    public bool EnsureInitialized(Type enumType, bool includeFields) {
 3161
 3162      if (enumType == null) {
 3163        throw new ArgumentNullException(nameof(enumType));
 3164      }
 3165
 3166      bool isEnum = enumType.IsEnum;
 3167
 3168      if (!isEnum && !typeof(FieldsMask).IsAssignableFrom(enumType)) {
 3169        throw new ArgumentException("Type must be an enum or FieldsMask", nameof(enumType));
 3170      }
 3171
 3172      // Already initialized
 3173      if (_enumType == enumType) {
 3174        return false;
 3175      }
 3176
 3177      if (isEnum) {
 3178        var enumUnderlyingType = Enum.GetUnderlyingType(enumType);
 3179        var rawValues          = Enum.GetValues(enumType);
 3180
 3181        _fields   = includeFields ? new FieldInfo[rawValues.Length] : null;
 3182        _names    = Enum.GetNames(enumType);
 3183        _values   = new Mask256[rawValues.Length];
 3184        _isFlags  = enumType.GetCustomAttribute<FlagsAttribute>() != null;
 3185        _enumType = enumType;
 3186
 3187        for (int i = 0; i < rawValues.Length; ++i) {
 3188          if (enumUnderlyingType == typeof(int)   ||
 3189              enumUnderlyingType == typeof(long)  ||
 3190              enumUnderlyingType == typeof(short) ||
 3191              enumUnderlyingType == typeof(byte)) {
 3192            _values[i] = Convert.ToInt64(rawValues.GetValue(i));
 3193          } else {
 3194            _values[i] = unchecked((long)Convert.ToUInt64(rawValues.GetValue(i)));
 3195          }
 3196
 3197          _allBitMask[0] |= _values[i][0];
 3198          if (includeFields) {
 3199            _fields[i] = enumType.GetField(_names[i], BindingFlags.Static | BindingFlags.Public);
 3200          }
 3201        }
 3202
 3203      } else {
 3204        // Handling for FieldsMask
 3205        var tType = enumType.GenericTypeArguments[0];
 3206
 3207        _fields   = tType.GetFields();
 3208        _names    = new string[_fields.Length];
 3209        _values   = new Mask256[_fields.Length];
 3210        _isFlags  = true;
 3211        _enumType = enumType;
 3212
 3213        for (int i = 0; i < _values.Length; i++) {
 3214          long value = (long)1 << i;
 3215          _allBitMask.SetBit(i, true);;
 3216          _values[i].SetBit(i, true); //  =   (long)1 << i;
 3217          _names[i]   =  _fields[i].Name;
 3218        }
 3219      }
 3220
 3221      for (int i = 0; i < _names.Length; ++i) {
 3222        _names[i] = ObjectNames.NicifyVariableName(_names[i]);
 3223      }
 3224
 3225      return true;
 3226    }
 3227
 3228    public void Draw(Rect position, SerializedProperty property, Type enumType, bool isEnum) {
 3229
 3230      if (property == null) {
 3231        throw new ArgumentNullException(nameof(property));
 3232      }
 3233
 3234      EnsureInitialized(enumType, false);
 3235      Mask256 currentValue;
 3236
 3237      if (isEnum) {
 3238        currentValue = new Mask256(
 3239          property.longValue
 3240        );
 3241      } else {
 3242        currentValue = new Mask256(
 3243          property.GetFixedBufferElementAtIndex(0).longValue,
 3244          property.GetFixedBufferElementAtIndex(1).longValue,
 3245          property.GetFixedBufferElementAtIndex(2).longValue,
 3246          property.GetFixedBufferElementAtIndex(3).longValue
 3247        );
 3248      }
 3249
 3250      _selectedIndices ??= new List<int>();
 3251      _selectedIndices.Clear();
 3252
 3253      // find out what to show
 3254      for (int i = 0; i < _values.Length; ++i) {
 3255        var value = _values[i];
 3256        if (_isFlags == false) {
 3257          if (currentValue[0]== value[0]) {
 3258            _selectedIndices.Add(i);
 3259            break;
 3260          }
 3261        } else if (Equals(currentValue & value, value)) {
 3262          _selectedIndices.Add(i);
 3263        }
 3264      }
 3265
 3266      string labelValue;
 3267      if (_selectedIndices.Count == 0) {
 3268        if (_isFlags && currentValue.IsNothing()) {
 3269          labelValue = "Nothing";
 3270        } else {
 3271          labelValue = "";
 3272        }
 3273      } else if (_selectedIndices.Count == 1) {
 3274        labelValue = _names[_selectedIndices[0]];
 3275      } else {
 3276        Debug.Assert(_isFlags);
 3277        if (_selectedIndices.Count == _values.Length) {
 3278          labelValue = "Everything";
 3279        } else {
 3280          var names = _names;
 3281          labelValue = string.Join(", ", _selectedIndices.Select(x => names[x]));
 3282        }
 3283      }
 3284
 3285      if (EditorGUI.DropdownButton(position, new GUIContent(labelValue), FocusType.Keyboard)) {
 3286        var values  = _values;
 3287        var indices = _selectedIndices;
 3288
 3289        if (_isFlags) {
 3290          var       allOptions = new[] { "Nothing", "Everything" }.Concat(_names).ToArray();
 3291          List<int> allIndices = new List<int>();
 3292          if (_selectedIndices.Count == 0) {
 3293            allIndices.Add(0); // nothing
 3294          }
 3295          else if (_selectedIndices.Count == _values.Length) {
 3296            allIndices.Add(1); // everything
 3297          }
 3298          allIndices.AddRange(_selectedIndices.Select(x => x + 2));
 3299
 3300          UnityInternal.EditorUtility.DisplayCustomMenu(position, allOptions, allIndices.ToArray(), (userData, options, 
 3301            if (selected == 0) {
 3302              // Clicked None
 3303              if (isEnum) {
 3304                property.longValue = 0;
 3305              }
 3306              else {
 3307                property.GetFixedBufferElementAtIndex(0).longValue = 0;
 3308                property.GetFixedBufferElementAtIndex(1).longValue = 0;
 3309                property.GetFixedBufferElementAtIndex(2).longValue = 0;
 3310                property.GetFixedBufferElementAtIndex(3).longValue = 0;
 3311              }
 3312            } else if (selected == 1) {
 3313              // Selected Everything
 3314              if (isEnum) {
 3315                property.longValue = 0;
 3316              } else {
 3317                property.GetFixedBufferElementAtIndex(0).longValue = 0;
 3318                property.GetFixedBufferElementAtIndex(1).longValue = 0;
 3319                property.GetFixedBufferElementAtIndex(2).longValue = 0;
 3320                property.GetFixedBufferElementAtIndex(3).longValue = 0;
 3321              }
 3322              foreach (var value in values) {
 3323                if (isEnum) {
 3324                  property.longValue |= value[0];
 3325                } else{
 3326                  property.GetFixedBufferElementAtIndex(0).longValue |= value[0];
 3327                  property.GetFixedBufferElementAtIndex(1).longValue |= value[1];
 3328                  property.GetFixedBufferElementAtIndex(2).longValue |= value[2];
 3329                  property.GetFixedBufferElementAtIndex(3).longValue |= value[3];
 3330                }
 3331              }
 3332            } else {
 3333              // Toggled a value
 3334              selected -= 2;
 3335              if (indices.Contains(selected)) {
 3336                if (isEnum) {
 3337                  property.longValue &= ~values[selected][0];
 3338                } else {
 3339                  property.GetFixedBufferElementAtIndex(0).longValue &= ~values[selected][0];
 3340                  property.GetFixedBufferElementAtIndex(1).longValue &= ~values[selected][1];
 3341                  property.GetFixedBufferElementAtIndex(2).longValue &= ~values[selected][2];
 3342                  property.GetFixedBufferElementAtIndex(3).longValue &= ~values[selected][3];
 3343                }
 3344              } else {
 3345                if (isEnum) {
 3346                  property.longValue |= (long)values[selected][0];
 3347                } else {
 3348                  property.GetFixedBufferElementAtIndex(0).longValue |= (long)values[selected][0];
 3349                  property.GetFixedBufferElementAtIndex(1).longValue |= (long)values[selected][1];
 3350                  property.GetFixedBufferElementAtIndex(2).longValue |= (long)values[selected][2];
 3351                  property.GetFixedBufferElementAtIndex(3).longValue |= (long)values[selected][3];
 3352                }
 3353              }
 3354            }
 3355
 3356            property.serializedObject.ApplyModifiedProperties();
 3357          }, null);
 3358        } else {
 3359          // non-flags enum
 3360          UnityInternal.EditorUtility.DisplayCustomMenu(position, _names, _selectedIndices.ToArray(), (userData, options
 3361            if (!indices.Contains(selected)) {
 3362              property.longValue = values[selected][0];
 3363              property.serializedObject.ApplyModifiedProperties();
 3364            }
 3365          }, null);
 3366        }
 3367      }
 3368    }
 3369  }
 3370}
 3371
 3372#endregion
 3373
 3374
 3375#region HashCodeUtilities.cs
 3376
 3377namespace Fusion.Editor {
 3378  internal static class HashCodeUtilities {
 3379    public const int InitialHash = (5381 << 16) + 5381;
 3380
 3381
 3382    /// <summary>
 3383    ///   This may only be deterministic on 64 bit systems.
 3384    /// </summary>
 3385    /// <param name="str"></param>
 3386    /// <param name="initialHash"></param>
 3387    /// <returns></returns>
 3388    public static int GetHashDeterministic(this string str, int initialHash = InitialHash) {
 3389      unchecked {
 3390        var hash1 = initialHash;
 3391        var hash2 = initialHash;
 3392
 3393        for (var i = 0; i < str.Length; i += 2) {
 3394          hash1 = ((hash1 << 5) + hash1) ^ str[i];
 3395          if (i == str.Length - 1) {
 3396            break;
 3397          }
 3398
 3399          hash2 = ((hash2 << 5) + hash2) ^ str[i + 1];
 3400        }
 3401
 3402        return hash1 + hash2 * 1566083941;
 3403      }
 3404    }
 3405
 3406    public static int CombineHashCodes(int a, int b) {
 3407      return ((a << 5) + a) ^ b;
 3408    }
 3409
 3410    public static int CombineHashCodes(int a, int b, int c) {
 3411      var t = ((a << 5) + a) ^ b;
 3412      return ((t << 5) + t) ^ c;
 3413    }
 3414
 3415    public static unsafe int GetArrayHashCode<T>(T* ptr, int length, int initialHash = InitialHash) where T : unmanaged 
 3416      var hash = initialHash;
 3417      for (var i = 0; i < length; ++i) {
 3418        hash = hash * 31 + ptr[i].GetHashCode();
 3419      }
 3420
 3421      return hash;
 3422    }
 3423
 3424    public static int GetHashCodeDeterministic(byte[] data, int initialHash = 0) {
 3425      var hash = initialHash;
 3426      for (var i = 0; i < data.Length; ++i) {
 3427        hash = hash * 31 + data[i];
 3428      }
 3429
 3430      return hash;
 3431    }
 3432
 3433    public static int GetHashCodeDeterministic(string data, int initialHash = 0) {
 3434      var hash = initialHash;
 3435      for (var i = 0; i < data.Length; ++i) {
 3436        hash = hash * 31 + data[i];
 3437      }
 3438
 3439      return hash;
 3440    }
 3441
 3442
 3443    public static unsafe int GetHashCodeDeterministic<T>(T data, int initialHash = 0) where T : unmanaged {
 3444      return GetHashCodeDeterministic(&data, initialHash);
 3445    }
 3446
 3447    public static unsafe int GetHashCodeDeterministic<T>(T* data, int initialHash = 0) where T : unmanaged {
 3448      var hash = initialHash;
 3449      var ptr  = (byte*)data;
 3450      for (var i = 0; i < sizeof(T); ++i) {
 3451        hash = hash * 31 + ptr[i];
 3452      }
 3453
 3454      return hash;
 3455    }
 3456  }
 3457}
 3458
 3459#endregion
 3460
 3461
 3462#region LazyAsset.cs
 3463
 3464namespace Fusion.Editor {
 3465  using System;
 3466  using System.Collections.Generic;
 3467  using UnityEngine;
 3468  using Object = UnityEngine.Object;
 3469
 3470  internal class LazyAsset<T> {
 3471    private T _value;
 3472    private Func<T> _factory;
 3473
 3474    public LazyAsset(Func<T> factory) {
 3475      _factory = factory;
 3476    }
 3477
 3478    public T Value {
 3479      get {
 3480        if (NeedsUpdate) {
 3481          lock (_factory) {
 3482            if (NeedsUpdate) {
 3483              _value = _factory();
 3484            }
 3485          }
 3486        }
 3487        return _value;
 3488      }
 3489    }
 3490
 3491    public static implicit operator T(LazyAsset<T> lazyAsset) {
 3492      return lazyAsset.Value;
 3493    }
 3494
 3495    public bool NeedsUpdate {
 3496      get {
 3497        if (_value is UnityEngine.Object obj) {
 3498          return !obj;
 3499        } else {
 3500          return _value == null;
 3501        }
 3502      }
 3503    }
 3504  }
 3505
 3506  internal class LazyGUIStyle {
 3507    private Func<List<Object>, GUIStyle> _factory;
 3508    private GUIStyle                     _value;
 3509    private List<Object>                 _dependencies = new List<Object>();
 3510
 3511    public LazyGUIStyle(Func<List<Object>, GUIStyle> factory) {
 3512      _factory = factory;
 3513    }
 3514
 3515    public static LazyGUIStyle Create(Func<List<Object>, GUIStyle> factory) {
 3516      return new LazyGUIStyle(factory);
 3517    }
 3518
 3519    public static implicit operator GUIStyle(LazyGUIStyle lazyAsset) {
 3520      return lazyAsset.Value;
 3521    }
 3522
 3523    public GUIStyle Value {
 3524      get {
 3525        if (NeedsUpdate) {
 3526          lock (_factory) {
 3527            if (NeedsUpdate) {
 3528              _dependencies.Clear();
 3529              _value = _factory(_dependencies);
 3530            }
 3531          }
 3532        }
 3533        return _value;
 3534      }
 3535    }
 3536
 3537    public bool NeedsUpdate {
 3538      get {
 3539        if (_value == null) {
 3540          return true;
 3541        }
 3542        foreach (var dependency in _dependencies) {
 3543          if (!dependency) {
 3544            return true;
 3545          }
 3546        }
 3547
 3548        return false;
 3549      }
 3550    }
 3551
 3552    public Vector2 CalcSize(GUIContent content)                                                                         
 3553    public void    Draw(Rect position, GUIContent content, bool isHover, bool isActive, bool on, bool hasKeyboardFocus) 
 3554    public void    Draw(Rect position, bool isHover, bool isActive, bool on, bool hasKeyboardFocus)                     
 3555  }
 3556
 3557  internal class LazyGUIContent {
 3558    private Func<List<Object>, GUIContent> _factory;
 3559    private GUIContent                     _value;
 3560    private List<Object>                   _dependencies = new List<Object>();
 3561
 3562    public LazyGUIContent(Func<List<Object>, GUIContent> factory) {
 3563      _factory = factory;
 3564    }
 3565
 3566    public static LazyGUIContent Create(Func<List<Object>, GUIContent> factory) {
 3567      return new LazyGUIContent(factory);
 3568    }
 3569
 3570    public static implicit operator GUIContent(LazyGUIContent lazyAsset) {
 3571      return lazyAsset.Value;
 3572    }
 3573
 3574    public GUIContent Value {
 3575      get {
 3576        if (NeedsUpdate) {
 3577          lock (_factory) {
 3578            if (NeedsUpdate) {
 3579              _dependencies.Clear();
 3580              _value = _factory(_dependencies);
 3581            }
 3582          }
 3583        }
 3584        return _value;
 3585      }
 3586    }
 3587
 3588    public bool NeedsUpdate {
 3589      get {
 3590        if (_value == null) {
 3591          return true;
 3592        }
 3593        foreach (var dependency in _dependencies) {
 3594          if (!dependency) {
 3595            return true;
 3596          }
 3597        }
 3598
 3599        return false;
 3600      }
 3601    }
 3602  }
 3603
 3604  internal static class LazyAsset {
 3605    public static LazyAsset<T> Create<T>(Func<T> factory) {
 3606      return new LazyAsset<T>(factory);
 3607    }
 3608  }
 3609}
 3610
 3611#endregion
 3612
 3613
 3614#region LogSettingsDrawer.cs
 3615
 3616namespace Fusion.Editor {
 3617  using System;
 3618  using System.Collections.Generic;
 3619  using System.Linq;
 3620  using UnityEditor;
 3621  using UnityEditor.Build;
 3622  using UnityEngine;
 3623
 3624
 3625  struct LogSettingsDrawer {
 3626    private static readonly Dictionary<string, LogLevel> _logLevels = new Dictionary<string, LogLevel>(StringComparer.Or
 3627      { "FUSION_LOGLEVEL_DEBUG", LogLevel.Debug },
 3628      { "FUSION_LOGLEVEL_INFO", LogLevel.Info },
 3629      { "FUSION_LOGLEVEL_WARN", LogLevel.Warn },
 3630      { "FUSION_LOGLEVEL_ERROR", LogLevel.Error },
 3631      { "FUSION_LOGLEVEL_NONE", LogLevel.None },
 3632    };
 3633
 3634    private static readonly Dictionary<string, TraceChannels> _enablingDefines = Enum.GetValues(typeof(TraceChannels))
 3635      .Cast<TraceChannels>()
 3636      .ToDictionary(x => $"FUSION_TRACE_{x.ToString().ToUpperInvariant()}", x => x);
 3637
 3638    private Dictionary<NamedBuildTarget, string[]> _defines;
 3639    private Lazy<GUIContent> _logLevelHelpContent;
 3640    private Lazy<GUIContent> _traceChannelsHelpContent;
 3641
 3642    void EnsureInitialized() {
 3643      if (_defines == null) {
 3644        UpdateDefines();
 3645      }
 3646
 3647      if (_logLevelHelpContent == null) {
 3648        _logLevelHelpContent = new Lazy<GUIContent>(() => {
 3649          var result = new GUIContent(FusionCodeDoc.FindEntry(typeof(LogLevel)) ?? new GUIContent());
 3650          result.text = ("This setting is applied with FUSION_LOGLEVEL_* defines.\n" + result.text).Trim();
 3651          return result;
 3652        });
 3653      }
 3654
 3655      if (_traceChannelsHelpContent == null) {
 3656        _traceChannelsHelpContent = new Lazy<GUIContent>(() => {
 3657          var result = new GUIContent(FusionCodeDoc.FindEntry(typeof(TraceChannels)) ?? new GUIContent());
 3658          result.text = ("This setting is applied with FUSION_TRACE_* defines.\n" + result.text).Trim();
 3659          return result;
 3660        });
 3661      }
 3662    }
 3663
 3664    public void DrawLayoutLevelEnumOnly(ScriptableObject editor) {
 3665      var activeLogLevel = GetActiveBuildTargetDefinedLogLevel();
 3666      var invalidActiveLogLevel = activeLogLevel == null;
 3667      EditorGUI.BeginChangeCheck();
 3668
 3669      using (new FusionEditorGUI.ShowMixedValueScope(invalidActiveLogLevel)) {
 3670        activeLogLevel = (LogLevel)EditorGUILayout.EnumPopup(activeLogLevel ?? LogLevel.Info);
 3671        Debug.Assert(activeLogLevel != null);
 3672      }
 3673
 3674      if (EditorGUI.EndChangeCheck()) {
 3675        SetLogLevel(activeLogLevel.Value);
 3676      }
 3677    }
 3678
 3679    public void DrawLogLevelEnum(Rect rect) {
 3680      EnsureInitialized();
 3681      var activeLogLevel = GetActiveBuildTargetDefinedLogLevel();
 3682      var invalidActiveLogLevel = activeLogLevel == null;
 3683      EditorGUI.BeginChangeCheck();
 3684
 3685      using (new FusionEditorGUI.ShowMixedValueScope(invalidActiveLogLevel)) {
 3686        activeLogLevel = (LogLevel)EditorGUI.EnumPopup(rect, activeLogLevel ?? LogLevel.Info);
 3687        Debug.Assert(activeLogLevel != null);
 3688      }
 3689
 3690      if (EditorGUI.EndChangeCheck()) {
 3691        SetLogLevel(activeLogLevel.Value);
 3692      }
 3693    }
 3694
 3695
 3696    public void DrawLayout(ScriptableObject editor, bool inlineHelp = true) {
 3697      EnsureInitialized();
 3698
 3699      {
 3700        var activeLogLevel = GetActiveBuildTargetDefinedLogLevel();
 3701        var invalidActiveLogLevel = activeLogLevel == null;
 3702        var rect = inlineHelp ? FusionEditorGUI.LayoutHelpPrefix(editor, "Log Level", _logLevelHelpContent.Value) : Edit
 3703        EditorGUI.BeginChangeCheck();
 3704
 3705        using (new FusionEditorGUI.ShowMixedValueScope(invalidActiveLogLevel)) {
 3706          activeLogLevel = (LogLevel)EditorGUI.EnumPopup(rect, "Log Level", activeLogLevel ?? LogLevel.Info);
 3707          Debug.Assert(activeLogLevel != null);
 3708        }
 3709
 3710        if (invalidActiveLogLevel) {
 3711          using (new FusionEditorGUI.WarningScope("Either FUSION_LOGLEVEL_* define is missing for the current build " +
 3712                                                        "target or there are more than one defined. Changing the value w
 3713                                                        "exactly one define <b>for each build target</b>.")) {
 3714          }
 3715        } else if (GetAllBuildTargetsDefinedLogLevel() == null) {
 3716          using (new FusionEditorGUI.WarningScope("Not all build targets have the same log level defined. Changing the v
 3717                                                        "there is exactly one define <b>for each build target</b>.")) {
 3718          }
 3719        }
 3720
 3721        if (EditorGUI.EndChangeCheck()) {
 3722          SetLogLevel(activeLogLevel.Value);
 3723        }
 3724      }
 3725
 3726      {
 3727        var activeTraceChannels = GetActiveBuildTargetDefinedTraceChannels();
 3728        var rect = inlineHelp ? FusionEditorGUI.LayoutHelpPrefix(editor, "Trace Channels", _traceChannelsHelpContent.Val
 3729
 3730        EditorGUI.BeginChangeCheck();
 3731
 3732        activeTraceChannels = (TraceChannels)EditorGUI.EnumFlagsField(rect, "Trace Channels", activeTraceChannels);
 3733
 3734        if (GetAllBuildTargetsDefinedTraceChannels() == null) {
 3735          using (new FusionEditorGUI.WarningScope("Not all build targets have the same trace channels defined. Changing 
 3736                                                        "the values are the same <b>for each build target</b>.")) {
 3737          }
 3738        }
 3739
 3740        if (EditorGUI.EndChangeCheck()) {
 3741          SetTraceChannels(activeTraceChannels);
 3742        }
 3743      }
 3744
 3745    }
 3746
 3747    private void SetLogLevel(LogLevel activeLogLevel) {
 3748      foreach (var kv in _defines) {
 3749        var target = kv.Key;
 3750        var defines = kv.Value;
 3751
 3752        string newDefine = null;
 3753        foreach (var (define, level) in _logLevels) {
 3754          if (level == activeLogLevel) {
 3755            newDefine = define;
 3756            continue;
 3757          }
 3758          ArrayUtility.Remove(ref defines, define);
 3759        }
 3760        ArrayUtility.Remove(ref defines, "FUSION_LOGLEVEL_TRACE");
 3761
 3762        Debug.Assert(newDefine != null);
 3763        if (!ArrayUtility.Contains(defines, newDefine)) {
 3764          ArrayUtility.Add(ref defines, newDefine);
 3765        }
 3766
 3767        PlayerSettings.SetScriptingDefineSymbols(target, string.Join(";", defines));
 3768      }
 3769
 3770      UpdateDefines();
 3771    }
 3772
 3773    private void SetTraceChannels(TraceChannels activeTraceChannels) {
 3774      List<string> definesToAdd = new List<string>();
 3775      List<string> definesToRemove = new List<string>();
 3776
 3777      foreach (var kv in _enablingDefines) {
 3778        var channel = kv.Value;
 3779        if (activeTraceChannels.HasFlag(channel)) {
 3780          definesToAdd.Add(kv.Key);
 3781        } else {
 3782          definesToRemove.Add(kv.Key);
 3783        }
 3784      }
 3785
 3786      foreach (var kv in _defines) {
 3787        var target = kv.Key;
 3788        var defines = kv.Value;
 3789
 3790        foreach (var d in definesToRemove) {
 3791          ArrayUtility.Remove(ref defines, d);
 3792        }
 3793
 3794        foreach (var d in definesToAdd) {
 3795          if (!ArrayUtility.Contains(defines, d)) {
 3796            ArrayUtility.Add(ref defines, d);
 3797          }
 3798        }
 3799
 3800        PlayerSettings.SetScriptingDefineSymbols(target, string.Join(";", defines));
 3801      }
 3802
 3803
 3804      UpdateDefines();
 3805    }
 3806
 3807    public LogLevel? GetActiveBuildTargetDefinedLogLevel() {
 3808      EnsureInitialized();
 3809      var activeBuildTarget = NamedBuildTarget.FromBuildTargetGroup(BuildPipeline.GetBuildTargetGroup(EditorUserBuildSet
 3810      return GetDefinedLogLevel(activeBuildTarget);
 3811    }
 3812
 3813    private TraceChannels GetActiveBuildTargetDefinedTraceChannels() {
 3814      var activeBuildTarget = NamedBuildTarget.FromBuildTargetGroup(BuildPipeline.GetBuildTargetGroup(EditorUserBuildSet
 3815      return GetDefinedTraceChannels(activeBuildTarget);
 3816    }
 3817
 3818
 3819    private LogLevel? GetAllBuildTargetsDefinedLogLevel() {
 3820      LogLevel? result = null;
 3821
 3822      foreach (var buildTarget in _defines.Keys) {
 3823        var targetLogLevel = GetDefinedLogLevel(buildTarget);
 3824
 3825        if (targetLogLevel == null) {
 3826          return null;
 3827        }
 3828
 3829        if (result == null) {
 3830          result = targetLogLevel;
 3831        } else if (result != targetLogLevel) {
 3832          return null;
 3833        }
 3834      }
 3835
 3836      return result;
 3837    }
 3838
 3839    private TraceChannels? GetAllBuildTargetsDefinedTraceChannels() {
 3840      TraceChannels? result = null;
 3841
 3842      foreach (var buildTarget in _defines.Keys) {
 3843        var targetLogLevel = GetDefinedTraceChannels(buildTarget);
 3844        if (result == null) {
 3845          result = targetLogLevel;
 3846        } else if (result != targetLogLevel) {
 3847          return null;
 3848        }
 3849      }
 3850
 3851      return result;
 3852    }
 3853
 3854    private LogLevel? GetDefinedLogLevel(NamedBuildTarget group) {
 3855      LogLevel? result = null;
 3856      var defines = _defines[group];
 3857
 3858      foreach (var define in defines) {
 3859        if (_logLevels.TryGetValue(define, out var logLevel)) {
 3860          if (result != null) {
 3861            if (result != logLevel) {
 3862              return null;
 3863            }
 3864          } else {
 3865            result = logLevel;
 3866          }
 3867        }
 3868      }
 3869
 3870      return result;
 3871    }
 3872
 3873    private TraceChannels GetDefinedTraceChannels(NamedBuildTarget group) {
 3874      var channels = default(TraceChannels);
 3875
 3876      var defines = _defines[group];
 3877      foreach (var define in defines) {
 3878        if (_enablingDefines.TryGetValue(define, out var channel)) {
 3879          channels |= channel;
 3880        }
 3881      }
 3882
 3883      return channels;
 3884    }
 3885
 3886    private void UpdateDefines() {
 3887      _defines = AssetDatabaseUtils.ValidBuildTargetGroups
 3888        .Select(NamedBuildTarget.FromBuildTargetGroup)
 3889        .ToDictionary(x => x, x => PlayerSettings.GetScriptingDefineSymbols(x).Split(';'));
 3890    }
 3891  }
 3892
 3893
 3894}
 3895
 3896#endregion
 3897
 3898
 3899#region PathUtils.cs
 3900
 3901namespace Fusion.Editor {
 3902  using System;
 3903
 3904  // TODO: this should be moved to the runtime part
 3905  static partial class PathUtils {
 3906
 3907    public static bool TryMakeRelativeToFolder(string path, string folderWithSlashes, out string result) {
 3908      var index = path.IndexOf(folderWithSlashes, StringComparison.Ordinal);
 3909
 3910      if (index < 0) {
 3911        result = string.Empty;
 3912        return false;
 3913      }
 3914
 3915      if (folderWithSlashes[0] != '/' && index > 0) {
 3916        result = string.Empty;
 3917        return false;
 3918      }
 3919
 3920      result = path.Substring(index + folderWithSlashes.Length);
 3921      return true;
 3922    }
 3923
 3924    [Obsolete("Use " + nameof(TryMakeRelativeToFolder) + " instead")]
 3925    public static bool MakeRelativeToFolder(string path, string folder, out string result) {
 3926      result = string.Empty;
 3927      var formattedPath = Normalize(path);
 3928      if (formattedPath.Equals(folder, StringComparison.Ordinal) ||
 3929          formattedPath.EndsWith("/" + folder)) {
 3930        return true;
 3931      }
 3932      var index = formattedPath.IndexOf(folder + "/", StringComparison.Ordinal);
 3933      var size  = folder.Length + 1;
 3934      if (index >= 0 && formattedPath.Length >= size) {
 3935        result = formattedPath.Substring(index + size, formattedPath.Length - index - size);
 3936        return true;
 3937      }
 3938      return false;
 3939    }
 3940
 3941    [Obsolete("Use Normalize instead")]
 3942    public static string MakeSane(string path) {
 3943      return Normalize(path);
 3944    }
 3945
 3946    public static string Normalize(string path) {
 3947      return path.Replace("\\", "/").Replace("//", "/").TrimEnd('\\', '/').TrimStart('\\', '/');
 3948    }
 3949
 3950    public static string GetPathWithoutExtension(string path) {
 3951      if (path == null)
 3952        return null;
 3953      int length;
 3954      if ((length = path.LastIndexOf('.')) == -1)
 3955        return path;
 3956      return path.Substring(0, length);
 3957    }
 3958
 3959  }
 3960}
 3961
 3962#endregion
 3963
 3964
 3965#region FusionCodeDoc.cs
 3966
 3967namespace Fusion.Editor {
 3968  using System;
 3969  using System.Collections.Generic;
 3970  using System.IO;
 3971  using System.Linq;
 3972  using System.Reflection;
 3973  using System.Text.RegularExpressions;
 3974  using System.Xml;
 3975  using UnityEditor;
 3976  using UnityEngine;
 3977
 3978  static class FusionCodeDoc {
 3979    public const string Label            = "FusionCodeDoc";
 3980    public const string Extension        = "xml";
 3981    public const string ExtensionWithDot = "." + Extension;
 3982
 3983    private static readonly Dictionary<string, CodeDoc> s_parsedCodeDocs = new();
 3984    private static readonly Dictionary<(string assemblyName, string memberKey), (GUIContent withoutType, GUIContent with
 3985
 3986    private static string CrefColor => EditorGUIUtility.isProSkin ? "#FFEECC" : "#664400";
 3987
 3988    public static GUIContent FindEntry(MemberInfo member, bool addTypeInfo = true) {
 3989      switch (member) {
 3990        case FieldInfo field:
 3991          return FindEntry(field, addTypeInfo);
 3992        case PropertyInfo property:
 3993          return FindEntry(property);
 3994        case MethodInfo method:
 3995          return FindEntry(method);
 3996        case Type type:
 3997          return FindEntry(type);
 3998        default:
 3999          throw new ArgumentOutOfRangeException(nameof(member));
 4000      }
 4001    }
 4002
 4003    public static GUIContent FindEntry(FieldInfo field, bool addTypeInfo = true) {
 4004      if (field == null) {
 4005        throw new ArgumentNullException(nameof(field));
 4006      }
 4007      return FindEntry(field, $"F:{SanitizeTypeName(field.DeclaringType)}.{field.Name}", addTypeInfo);
 4008    }
 4009
 4010    public static GUIContent FindEntry(PropertyInfo property) {
 4011      if (property == null) {
 4012        throw new ArgumentNullException(nameof(property));
 4013      }
 4014      return FindEntry(property, $"P:{SanitizeTypeName(property.DeclaringType)}.{property.Name}");
 4015    }
 4016
 4017    public static GUIContent FindEntry(MethodInfo method) {
 4018      if (method == null) {
 4019        throw new ArgumentNullException(nameof(method));
 4020      }
 4021      return FindEntry(method, $"M:{SanitizeTypeName(method.DeclaringType)}.{method.Name}");
 4022    }
 4023
 4024    public static GUIContent FindEntry(Type type) {
 4025      if (type == null) {
 4026        throw new ArgumentNullException(nameof(type));
 4027      }
 4028      return FindEntry(type, $"T:{SanitizeTypeName(type)}");
 4029    }
 4030
 4031    private static GUIContent FindEntry(MemberInfo member, string key, bool addTypeInfo = true) {
 4032
 4033      Assembly assembly;
 4034      if (member is Type type) {
 4035        assembly = type.Assembly;
 4036      } else {
 4037        FusionEditorLog.Assert(member.DeclaringType != null);
 4038        assembly = member.DeclaringType.Assembly;
 4039      }
 4040
 4041      var assemblyName = assembly.GetName().Name;
 4042      FusionEditorLog.Assert(assemblyName != null);
 4043
 4044      if (s_guiContentCache.TryGetValue((assemblyName, key), out var content)) {
 4045        return addTypeInfo ? content.withType : content.withoutType;
 4046      }
 4047
 4048      if (TryGetEntry(key, out var entry, assemblyName: assemblyName)) {
 4049        // at this point we've got docs or not, need to save it now - in case returnType code doc search tries
 4050        // to load the same member info, which might happen; same for inheritdoc
 4051        content.withoutType = new GUIContent(entry.Summary ?? string.Empty, entry.Tooltip ?? string.Empty);
 4052        content.withType    = content.withoutType;
 4053      }
 4054
 4055      s_guiContentCache.Add((assemblyName, key), content);
 4056
 4057      if (!string.IsNullOrEmpty(entry.InheritDocKey)) {
 4058        // need to resolve the inheritdoc
 4059        FusionEditorLog.Assert(entry.InheritDocKey != key);
 4060        if (TryResolveInheritDoc(entry.InheritDocKey, out var rootEntry)) {
 4061          content.withoutType = new GUIContent(rootEntry.Summary, rootEntry.Tooltip);
 4062          content.withType    = content.withoutType;
 4063          s_guiContentCache[(assemblyName, key)] = content;
 4064        }
 4065      }
 4066
 4067      // now add type info
 4068      Type returnType = (member as FieldInfo)?.FieldType ?? (member as PropertyInfo)?.PropertyType;
 4069      if (returnType != null) {
 4070        var    typeEntry   = FindEntry(returnType);
 4071        string typeSummary = "";
 4072
 4073        if (typeEntry != null) {
 4074          typeSummary += $"\n\n<color={CrefColor}>[{returnType.Name}]</color> {typeEntry}";
 4075        }
 4076
 4077        if (returnType.IsEnum) {
 4078          // find all the enum values
 4079          foreach (var enumValue in returnType.GetFields(BindingFlags.Static | BindingFlags.Public)) {
 4080            var enumValueEntry = FindEntry(enumValue, addTypeInfo: false);
 4081            if (enumValueEntry != null) {
 4082              typeSummary += $"\n\n<color={CrefColor}>[{returnType.Name}.{enumValue.Name}]</color> {enumValueEntry}";
 4083            }
 4084          }
 4085        }
 4086
 4087        if (typeSummary.Length > 0) {
 4088          content.withType = AppendContent(content.withType, typeSummary);
 4089          s_guiContentCache[(assemblyName, key)] = content;
 4090        }
 4091      }
 4092
 4093      return addTypeInfo ? content.withType : content.withoutType;
 4094
 4095      GUIContent AppendContent(GUIContent existing, string append) {
 4096        return new GUIContent((existing?.text + append).Trim('\n'), existing?.tooltip ?? string.Empty);
 4097      }
 4098    }
 4099
 4100    private static bool TryResolveInheritDoc(string key, out MemberInfoEntry entry) {
 4101      // difficult to tell which assembly this comes from; just check in them all
 4102      // also make sure we're not in a loop
 4103      var visited   = new HashSet<string>();
 4104      var currentKey = key;
 4105
 4106      for (;;) {
 4107        if (!visited.Add(currentKey)) {
 4108          FusionEditorLog.Error($"Inheritdoc loop detected for {key}");
 4109          break;
 4110        }
 4111
 4112        if (!TryGetEntry(currentKey, out var currentEntry)) {
 4113          break;
 4114        }
 4115
 4116        if (string.IsNullOrEmpty(currentEntry.InheritDocKey)) {
 4117          entry = currentEntry;
 4118          return true;
 4119        }
 4120
 4121        currentKey = currentEntry.InheritDocKey;
 4122      }
 4123
 4124      entry = default;
 4125      return false;
 4126    }
 4127
 4128    private static bool TryGetEntry(string key, out MemberInfoEntry entry, string assemblyName = null) {
 4129      foreach (var path in AssetDatabase.FindAssets($"l:{Label} t:TextAsset")
 4130                 .Select(x => AssetDatabase.GUIDToAssetPath(x))) {
 4131
 4132        if (assemblyName != null) {
 4133          if (!Path.GetFileNameWithoutExtension(path).Contains(assemblyName, StringComparison.OrdinalIgnoreCase)) {
 4134            continue;
 4135          }
 4136        }
 4137
 4138        // has this path been parsed already?
 4139        if (!s_parsedCodeDocs.TryGetValue(path, out var parsedCodeDoc)) {
 4140          s_parsedCodeDocs.Add(path, null);
 4141
 4142          FusionEditorLog.Trace($"Trying to parse {path} for {key}");
 4143          if (TryParseCodeDoc(path, out parsedCodeDoc)) {
 4144            s_parsedCodeDocs[path] = parsedCodeDoc;
 4145          } else {
 4146            FusionEditorLog.Trace($"Failed to parse {path}");
 4147          }
 4148        }
 4149
 4150        if (parsedCodeDoc != null) {
 4151          if (assemblyName != null && parsedCodeDoc.AssemblyName != assemblyName) {
 4152            // wrong assembly!
 4153            continue;
 4154          }
 4155          if (parsedCodeDoc.Entries.TryGetValue(key, out entry)) {
 4156            return true;
 4157          }
 4158        }
 4159      }
 4160
 4161      entry = default;
 4162      return false;
 4163    }
 4164
 4165    private static string SanitizeTypeName(Type type) {
 4166      var t = type;
 4167      if (type.IsGenericType) {
 4168        t = type.GetGenericTypeDefinition();
 4169      }
 4170      FusionEditorLog.Assert(t != null);
 4171      return t.FullName.Replace('+', '.');
 4172    }
 4173
 4174    public static void InvalidateCache() {
 4175      s_parsedCodeDocs.Clear();
 4176      s_guiContentCache.Clear();
 4177    }
 4178
 4179    private static bool TryParseCodeDoc(string path, out CodeDoc result) {
 4180      var xmlDoc = new XmlDocument();
 4181
 4182      try {
 4183        xmlDoc.Load(path);
 4184      } catch (Exception e) {
 4185        FusionEditorLog.Error($"Failed to load {path}: {e}");
 4186        result = null;
 4187        return false;
 4188      }
 4189
 4190      FusionEditorLog.Assert(xmlDoc.DocumentElement != null);
 4191      var assemblyName = xmlDoc.DocumentElement.SelectSingleNode("assembly")
 4192       ?.SelectSingleNode("name")
 4193       ?.FirstChild
 4194       ?.Value;
 4195
 4196      if (assemblyName == null) {
 4197        result = null;
 4198        return false;
 4199      }
 4200
 4201      var members = xmlDoc.DocumentElement.SelectSingleNode("members")
 4202        ?.SelectNodes("member");
 4203
 4204      if (members == null) {
 4205        result = null;
 4206        return false;
 4207      }
 4208
 4209      var entries = new Dictionary<string, MemberInfoEntry>();
 4210
 4211      foreach (XmlNode node in members) {
 4212        FusionEditorLog.Assert(node.Attributes != null);
 4213        var key     = node.Attributes["name"].Value;
 4214        var inherit = node.SelectSingleNode("inheritdoc");
 4215        if (inherit != null) {
 4216
 4217          // hold on to the ref, will need to resolve it later
 4218          FusionEditorLog.Assert(inherit.Attributes != null);
 4219          var cref = inherit.Attributes["cref"]?.Value;
 4220          if (!string.IsNullOrEmpty(cref)) {
 4221            entries.Add(key, new MemberInfoEntry() {
 4222              InheritDocKey = cref
 4223            });
 4224            continue;
 4225          }
 4226        }
 4227
 4228        var summary = node.SelectSingleNode("summary")?.InnerXml.Trim();
 4229        if (summary == null) {
 4230          continue;
 4231        }
 4232
 4233        // remove generic indicator
 4234        summary = summary.Replace("`1", "");
 4235
 4236        // fork tooltip and help summaries
 4237        var help    = Reformat(summary, false);
 4238        var tooltip = Reformat(summary, true);
 4239
 4240        entries.Add(key, new MemberInfoEntry() {
 4241          Summary = help,
 4242          Tooltip = tooltip
 4243        });
 4244      }
 4245
 4246      result = new CodeDoc() {
 4247        AssemblyName = assemblyName,
 4248        Entries      = entries,
 4249      };
 4250      return true;
 4251    }
 4252
 4253    private static string Reformat(string summary, bool forTooltip) {
 4254      // Tooltips don't support formatting tags. Inline help does.
 4255      if (forTooltip) {
 4256        summary = Regexes.SeeWithCref.Replace(summary, "$1");
 4257        summary = Regexes.See.Replace(summary, "$1");
 4258        summary = Regexes.XmlEmphasizeBrackets.Replace(summary, "$1");
 4259      } else {
 4260        var colorstring = $"<color={CrefColor}>$1</color>";
 4261        summary = Regexes.SeeWithCref.Replace(summary, colorstring);
 4262        summary = Regexes.See.Replace(summary, colorstring);
 4263      }
 4264
 4265
 4266      summary = Regexes.XmlCodeBracket.Replace(summary, "$1");
 4267
 4268      // Reduce all sequential whitespace characters into a single space.
 4269      summary = Regexes.WhitespaceString.Replace(summary, " ");
 4270
 4271      // Turn <para> and <br> into line breaks
 4272      summary = Regex.Replace(summary, @"</para>\s?<para>", "\n\n"); // prevent back to back paras from producing 4 line
 4273      summary = Regex.Replace(summary, @"</?para>\s?", "\n\n");
 4274      summary = Regex.Replace(summary, @"</?br\s?/?>\s?", "\n\n");
 4275
 4276      // handle lists
 4277      for (;;) {
 4278        var listMatch = Regexes.BulletPointList.Match(summary);
 4279        if (!listMatch.Success) {
 4280          break;
 4281        }
 4282        var innerText = listMatch.Groups[1].Value;
 4283        innerText = Regexes.ListItemBracket.Replace(innerText, $"\n\u2022 $1");
 4284        summary = summary.Substring(0, listMatch.Index) + innerText + summary.Substring(listMatch.Index + listMatch.Leng
 4285      }
 4286
 4287
 4288      // unescape <>
 4289      summary = summary.Replace("&lt;", "<");
 4290      summary = summary.Replace("&gt;", ">");
 4291      summary = summary.Replace("&amp;", "&");
 4292
 4293      summary = summary.Trim();
 4294
 4295      return summary;
 4296    }
 4297
 4298    private struct MemberInfoEntry {
 4299      public string Summary;
 4300      public string Tooltip;
 4301      public string InheritDocKey;
 4302    }
 4303
 4304    private class CodeDoc {
 4305      public string                              AssemblyName;
 4306      public Dictionary<string, MemberInfoEntry> Entries;
 4307    }
 4308
 4309    private class Postprocessor : AssetPostprocessor {
 4310      private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, 
 4311        foreach (var path in importedAssets) {
 4312          if (!path.StartsWith("Assets/") || !path.EndsWith(ExtensionWithDot)) {
 4313            continue;
 4314          }
 4315
 4316          if (AssetDatabaseUtils.HasLabel(path, Label)) {
 4317            FusionEditorLog.Trace($"Code doc {path} was imported, refreshing");
 4318            InvalidateCache();
 4319            continue;
 4320          }
 4321
 4322          // is there a dll with the same name?
 4323          if (!File.Exists(path.Substring(0, path.Length - ExtensionWithDot.Length) + ".dll")) {
 4324            FusionEditorLog.Trace($"No DLL next to {path}, not going to add label {Label}.");
 4325            continue;
 4326          }
 4327
 4328          if (!path.StartsWith("Assets/Photon/")) {
 4329            FusionEditorLog.Trace($"DLL is out of supported folder, not going to add label: {path}");
 4330            continue;
 4331          }
 4332
 4333          FusionEditorLog.Trace($"Detected a dll next to {path}, applying label and refreshing.");
 4334          AssetDatabaseUtils.SetLabel(path, Label, true);
 4335          InvalidateCache();
 4336        }
 4337      }
 4338    }
 4339
 4340    private static class Regexes {
 4341      public static readonly Regex SeeWithCref          = new(@"<see\w* (?:cref|langword)=""(?:\w: ?)?([\w\.\d]*?)(?:\(.
 4342      public static readonly Regex See                  = new(@"<see\w* .*>([\w\.\d]*)<\/see\w*>", RegexOptions.None);
 4343      public static readonly Regex WhitespaceString     = new(@"\s+");
 4344      public static readonly Regex XmlCodeBracket       = new(@"<code>([\s\S]*?)</code>");
 4345      public static readonly Regex XmlEmphasizeBrackets = new(@"<\w>([\s\S]*?)</\w>");
 4346      public static readonly Regex BulletPointList      = new(@"<list type=""bullet"">([\s\S]*?)</list>");
 4347      public static readonly Regex ListItemBracket      = new(@"<item>\s*<description>([\s\S]*?)</description>\s*</item>
 4348    }
 4349  }
 4350}
 4351
 4352#endregion
 4353
 4354
 4355#region FusionEditor.cs
 4356
 4357namespace Fusion.Editor {
 4358  using UnityEditor;
 4359
 4360  /// <summary>
 4361  /// Base class for all Photon Common editors. Supports <see cref="EditorButtonAttribute"/> and <see cref="ScriptHelpAt
 4362  /// </summary>
 4363  public abstract class FusionEditor :
 4364#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
 4365    Sirenix.OdinInspector.Editor.OdinEditor
 4366#else
 4367    UnityEditor.Editor
 4368#endif
 4369  {
 4370    private EditorButtonDrawer _buttonDrawer;
 4371
 4372    /// <summary>
 4373    /// Prepares the editor by initializing the script header drawer.
 4374    /// </summary>
 4375    protected void PrepareOnInspectorGUI() {
 4376      FusionEditorGUI.InjectScriptHeaderDrawer(this);
 4377    }
 4378
 4379    /// <summary>
 4380    /// Draws the editor buttons.
 4381    /// </summary>
 4382    protected void DrawEditorButtons() {
 4383      _buttonDrawer.Draw(this);
 4384    }
 4385
 4386    /// <inheritdoc/>
 4387    public override void OnInspectorGUI() {
 4388      PrepareOnInspectorGUI();
 4389      base.OnInspectorGUI();
 4390      DrawEditorButtons();
 4391    }
 4392
 4393    /// <summary>
 4394    /// Draws the script property field.
 4395    /// </summary>
 4396    protected void DrawScriptPropertyField() {
 4397      FusionEditorGUI.ScriptPropertyField(this);
 4398    }
 4399
 4400#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
 4401    /// <summary>
 4402    /// Draws the default inspector.
 4403    /// </summary>
 4404    public new bool DrawDefaultInspector() {
 4405      EditorGUI.BeginChangeCheck();
 4406      base.DrawDefaultInspector();
 4407      return EditorGUI.EndChangeCheck();
 4408    }
 4409#else
 4410    /// <summary>
 4411    /// Empty implementations, provided for compatibility with OdinEditor class.
 4412    /// </summary>
 4413    protected virtual void OnEnable() {
 4414    }
 4415
 4416    /// <summary>
 4417    /// Empty implementations, provided for compatibility with OdinEditor class.
 4418    /// </summary>
 4419    protected virtual void OnDisable() {
 4420    }
 4421#endif
 4422  }
 4423}
 4424
 4425
 4426#endregion
 4427
 4428
 4429#region FusionEditorGUI.InlineHelp.cs
 4430
 4431namespace Fusion.Editor {
 4432  using System;
 4433  using System.Collections.Generic;
 4434  using System.Linq;
 4435  using System.Reflection;
 4436  using UnityEditor;
 4437  using UnityEngine;
 4438
 4439  static partial class FusionEditorGUI {
 4440    private const float SCROLL_WIDTH     = 16f;
 4441    private const float LEFT_HELP_INDENT = 8f;
 4442
 4443    private static (object, string) s_expandedHelp;
 4444
 4445    internal static Rect GetInlineHelpButtonRect(Rect position, bool expectFoldout = true, bool forScriptHeader = false)
 4446      var style = FusionEditorSkin.HelpButtonStyle;
 4447
 4448      float width = style.fixedWidth <= 0 ? 16.0f : style.fixedWidth;
 4449      float height = style.fixedHeight <= 0 ? 16.0f : style.fixedHeight;
 4450
 4451      // this 2 lower than line height, but makes it look better
 4452      const float FirstLineHeight = 16;
 4453
 4454      int offsetY    = forScriptHeader ? -1 : 1;
 4455
 4456      var buttonRect = new Rect(position.x - width, position.y + (FirstLineHeight - height) / 2 + + offsetY, width, heig
 4457      using (new EditorGUI.IndentLevelScope(expectFoldout ? -1 : 0)) {
 4458        buttonRect.x = EditorGUI.IndentedRect(buttonRect).x;
 4459        // give indented items a little extra padding - no need for them to be so crammed
 4460        if (buttonRect.x > 8) {
 4461          buttonRect.x -= 2;
 4462        }
 4463      }
 4464
 4465      return buttonRect;
 4466    }
 4467
 4468
 4469    internal static bool DrawInlineHelpButton(Rect buttonRect, bool state, bool doButton = true, bool doIcon = true) {
 4470
 4471      var style = FusionEditorSkin.HelpButtonStyle;
 4472
 4473      var result = false;
 4474      if (doButton) {
 4475        EditorGUIUtility.AddCursorRect(buttonRect, MouseCursor.Link);
 4476        using (new EnabledScope(true)) {
 4477          result = GUI.Button(buttonRect, state ? InlineHelpStyle.HideInlineContent : InlineHelpStyle.ShowInlineContent,
 4478        }
 4479      }
 4480
 4481      if (doIcon) {
 4482        // paint over what the inspector has drawn
 4483        if (Event.current.type == EventType.Repaint) {
 4484          style.Draw(buttonRect, false, false, state, false);
 4485        }
 4486      }
 4487
 4488      return result;
 4489    }
 4490
 4491    internal static Vector2 GetInlineBoxSize(GUIContent content) {
 4492
 4493      // const int InlineBoxExtraHeight = 4;
 4494
 4495      var outerStyle = FusionEditorSkin.InlineBoxFullWidthStyle;
 4496      var innerStyle = FusionEditorSkin.RichLabelStyle;
 4497
 4498      var outerMargin  = outerStyle.margin;
 4499      var outerPadding = outerStyle.padding;
 4500
 4501      var width = UnityInternal.EditorGUIUtility.contextWidth - outerMargin.left - outerMargin.right;
 4502
 4503      // well... we do this, because there's no way of knowing the indent and scroll bar existence
 4504      // when property height is calculated
 4505      width -= 25.0f;
 4506
 4507      if (content == null || width <= 0) {
 4508        return default;
 4509      }
 4510
 4511      width -= outerPadding.left + outerPadding.right;
 4512
 4513      var height = innerStyle.CalcHeight(content, width);
 4514
 4515      // assume min height
 4516      height = Mathf.Max(height, EditorGUIUtility.singleLineHeight);
 4517
 4518      // add back all the padding
 4519      height += outerPadding.top + outerPadding.bottom;
 4520      height += outerMargin.top + outerMargin.bottom;
 4521
 4522      return new Vector2(width, Mathf.Max(0, height));
 4523    }
 4524
 4525    internal static Rect DrawInlineBoxUnderProperty(GUIContent content, Rect propertyRect, Color color, bool drawSelecto
 4526      using (new EnabledScope(true)) {
 4527
 4528        var boxSize = GetInlineBoxSize(content);
 4529
 4530        if (Event.current.type == EventType.Repaint && boxSize.y > 0) {
 4531          var boxMargin = FusionEditorSkin.InlineBoxFullWidthStyle.margin;
 4532
 4533          var boxRect = new Rect() {
 4534            x      = boxMargin.left,
 4535            y      = propertyRect.yMax - boxSize.y,
 4536            width  = UnityInternal.EditorGUIUtility.contextWidth - boxMargin.horizontal,
 4537            height = boxSize.y,
 4538          };
 4539
 4540          using (new BackgroundColorScope(color)) {
 4541            FusionEditorSkin.InlineBoxFullWidthStyle.Draw(boxRect, false, false, false, false);
 4542
 4543            var labelRect = boxRect;
 4544            labelRect = FusionEditorSkin.InlineBoxFullWidthStyle.padding.Remove(labelRect);
 4545            FusionEditorSkin.RichLabelStyle.Draw(labelRect, content, false, false, false, false);
 4546
 4547            if (drawSelector) {
 4548              var selectorMargin = FusionEditorSkin.InlineSelectorStyle.margin;
 4549
 4550              var selectorRect = new Rect() {
 4551                x      = selectorMargin.left,
 4552                y      = propertyRect.y - selectorMargin.top,
 4553                width  = propertyRect.x - selectorMargin.horizontal,
 4554                height = propertyRect.height - boxSize.y - selectorMargin.bottom,
 4555              };
 4556
 4557              if (hasFoldout) {
 4558                selectorRect.width -= 20.0f;
 4559              }
 4560
 4561              FusionEditorSkin.InlineSelectorStyle.Draw(selectorRect, false, false, false, false);
 4562            }
 4563          }
 4564        }
 4565
 4566        propertyRect.height -= boxSize.y;
 4567        return propertyRect;
 4568      }
 4569    }
 4570
 4571
 4572    internal static void DrawScriptHeaderBackground(Rect position, Color color) {
 4573      if (Event.current.type != EventType.Repaint) {
 4574        return;
 4575      }
 4576
 4577      var style     = FusionEditorSkin.ScriptHeaderBackgroundStyle;
 4578      var boxMargin = style.margin;
 4579
 4580      var boxRect = new Rect() {
 4581        x      = boxMargin.left,
 4582        y      = position.y - boxMargin.top,
 4583        width  = UnityInternal.EditorGUIUtility.contextWidth - boxMargin.horizontal,
 4584        height = position.height + boxMargin.bottom,
 4585      };
 4586
 4587      using (new BackgroundColorScope(color)) {
 4588        style.Draw(boxRect, false, false, false, false);
 4589      }
 4590    }
 4591
 4592    internal static void DrawScriptHeaderIcon(Rect position) {
 4593      if (Event.current.type != EventType.Repaint) {
 4594        return;
 4595      }
 4596
 4597      var style     = FusionEditorSkin.ScriptHeaderIconStyle;
 4598      var boxMargin = style.margin;
 4599      var boxRect   = boxMargin.Remove(position);
 4600
 4601      style.Draw(boxRect, false, false, false, false);
 4602    }
 4603
 4604    internal static bool InjectScriptHeaderDrawer(Editor editor)                               => InjectScriptHeaderDraw
 4605    internal static bool InjectScriptHeaderDrawer(Editor editor, out ScriptFieldDrawer drawer) => InjectScriptHeaderDraw
 4606    internal static bool InjectScriptHeaderDrawer(SerializedObject serializedObject)           => InjectScriptHeaderDraw
 4607
 4608    internal static bool InjectScriptHeaderDrawer(SerializedObject serializedObject, out ScriptFieldDrawer drawer) {
 4609      var sp       = serializedObject.FindPropertyOrThrow(ScriptPropertyName);
 4610      var rootType = serializedObject.targetObject.GetType();
 4611
 4612      var injected = TryInjectDrawer(sp, null, () => null, () => new ScriptFieldDrawer(), out drawer);
 4613      if (drawer.attribute == null) {
 4614        UnityInternal.PropertyDrawer.SetAttribute(drawer, rootType.GetCustomAttributes<ScriptHelpAttribute>(true).Single
 4615      }
 4616
 4617      return injected;
 4618    }
 4619
 4620    internal static void SetScriptFieldHidden(Editor editor, bool hidden) {
 4621      var sp = editor.serializedObject.FindPropertyOrThrow(ScriptPropertyName);
 4622      TryInjectDrawer(sp, null, () => null, () => new ScriptFieldDrawer(), out var drawer);
 4623      drawer.ForceHide = hidden;
 4624    }
 4625
 4626    internal static Rect LayoutHelpPrefix(Editor editor, SerializedProperty property) {
 4627      var fieldInfo = UnityInternal.ScriptAttributeUtility.GetFieldInfoFromProperty(property, out _);
 4628      if (fieldInfo == null) {
 4629        return EditorGUILayout.GetControlRect(true);
 4630      }
 4631
 4632      var help = FusionCodeDoc.FindEntry(fieldInfo);
 4633      return LayoutHelpPrefix(editor, property.propertyPath, help);
 4634    }
 4635
 4636    internal static Rect LayoutHelpPrefix(ScriptableObject editor, MemberInfo memberInfo) {
 4637      var help = FusionCodeDoc.FindEntry(memberInfo);
 4638      return LayoutHelpPrefix(editor, memberInfo.Name, help);
 4639    }
 4640
 4641    internal static Rect LayoutHelpPrefix(ScriptableObject editor, string path, GUIContent help) {
 4642      var rect = EditorGUILayout.GetControlRect(true);
 4643
 4644      if (help == null) {
 4645        return rect;
 4646      }
 4647
 4648      var buttonRect  = GetInlineHelpButtonRect(rect, false);
 4649      var wasExpanded = IsHelpExpanded(editor, path);
 4650
 4651      if (wasExpanded) {
 4652        var helpSize = GetInlineBoxSize(help);
 4653        var r        = EditorGUILayout.GetControlRect(false, helpSize.y);
 4654        r.y      =  rect.y;
 4655        r.height += rect.height;
 4656        DrawInlineBoxUnderProperty(help, r, FusionEditorSkin.HelpInlineBoxColor, true);
 4657      }
 4658
 4659      if (DrawInlineHelpButton(buttonRect, wasExpanded, doButton: true, doIcon: true)) {
 4660        SetHelpExpanded(editor, path, !wasExpanded);
 4661      }
 4662
 4663      return rect;
 4664    }
 4665
 4666    private static void AddDrawer(SerializedProperty property, PropertyDrawer drawer) {
 4667      var handler = UnityInternal.ScriptAttributeUtility.GetHandler(property);
 4668
 4669      if (handler.m_PropertyDrawers == null) {
 4670        handler.m_PropertyDrawers = new List<PropertyDrawer>();
 4671      }
 4672
 4673      InsertPropertyDrawerByAttributeOrder(handler.m_PropertyDrawers, drawer);
 4674    }
 4675
 4676    private static bool TryInjectDrawer<DrawerType>(SerializedProperty property, FieldInfo field, Func<PropertyAttribute
 4677      where DrawerType : PropertyDrawer {
 4678
 4679      var handler = UnityInternal.ScriptAttributeUtility.GetHandler(property);
 4680
 4681      drawer = GetPropertyDrawer<DrawerType>(handler.m_PropertyDrawers);
 4682      if (drawer != null) {
 4683        return false;
 4684      }
 4685
 4686      if (handler.Equals(UnityInternal.ScriptAttributeUtility.sharedNullHandler)) {
 4687        // need to add one?
 4688        handler = UnityInternal.PropertyHandler.New();
 4689        UnityInternal.ScriptAttributeUtility.propertyHandlerCache.SetHandler(property, handler);
 4690      }
 4691
 4692      var attribute = attributeFactory();
 4693
 4694      drawer = drawerFactory();
 4695      FusionEditorLog.Assert(drawer != null, "drawer != null");
 4696      UnityInternal.PropertyDrawer.SetAttribute(drawer, attribute);
 4697      UnityInternal.PropertyDrawer.SetFieldInfo(drawer, field);
 4698
 4699      AddDrawer(property, drawer);
 4700
 4701      return true;
 4702    }
 4703
 4704    internal static bool IsHelpExpanded(object id, string path) {
 4705      return s_expandedHelp == (id, path);
 4706    }
 4707
 4708    internal static void SetHelpExpanded(object id, string path, bool value) {
 4709      if (value) {
 4710        s_expandedHelp = (id, path);
 4711      } else {
 4712        s_expandedHelp = default;
 4713      }
 4714    }
 4715
 4716    private static bool HasPropertyDrawer<T>(IEnumerable<PropertyDrawer> orderedDrawers) where T : PropertyDrawer {
 4717      return orderedDrawers?.Any(x => x is T) ?? false;
 4718    }
 4719
 4720    private static T GetPropertyDrawer<T>(IEnumerable<PropertyDrawer> orderedDrawers) where T : PropertyDrawer {
 4721      return orderedDrawers?.OfType<T>().FirstOrDefault();
 4722    }
 4723
 4724    internal static int InsertPropertyDrawerByAttributeOrder<T>(List<T> orderedDrawers, T drawer) where T : PropertyDraw
 4725      if (orderedDrawers == null) {
 4726        throw new ArgumentNullException(nameof(orderedDrawers));
 4727      }
 4728
 4729      if (drawer == null) {
 4730        throw new ArgumentNullException(nameof(drawer));
 4731      }
 4732
 4733      var index = orderedDrawers.BinarySearch(drawer, PropertyDrawerOrderComparer.Instance);
 4734      if (index < 0) {
 4735        index = ~index;
 4736      }
 4737
 4738      orderedDrawers.Insert(index, drawer);
 4739      return index;
 4740    }
 4741
 4742    internal static class InlineHelpStyle {
 4743      public const  float      MarginOuter       = 16.0f;
 4744      public static GUIContent HideInlineContent = new("", "Hide");
 4745      public static GUIContent ShowInlineContent = new("", "");
 4746    }
 4747
 4748    internal static class LazyAuto {
 4749      public static LazyAuto<T> Create<T>(Func<T> valueFactory) {
 4750        return new LazyAuto<T>(valueFactory);
 4751      }
 4752    }
 4753
 4754    internal class LazyAuto<T> : Lazy<T> {
 4755      public LazyAuto(Func<T> valueFactory) : base(valueFactory) {
 4756      }
 4757
 4758      public static implicit operator T(LazyAuto<T> lazy) {
 4759        return lazy.Value;
 4760      }
 4761    }
 4762
 4763
 4764    private class PropertyDrawerOrderComparer : IComparer<PropertyDrawer> {
 4765      public static readonly PropertyDrawerOrderComparer Instance = new();
 4766
 4767      public int Compare(PropertyDrawer x, PropertyDrawer y) {
 4768        var ox = x.attribute?.order ?? int.MaxValue;
 4769        var oy = y.attribute?.order ?? int.MaxValue;
 4770        return ox - oy;
 4771      }
 4772    }
 4773  }
 4774}
 4775
 4776#endregion
 4777
 4778
 4779#region FusionEditorGUI.Odin.cs
 4780
 4781namespace Fusion.Editor {
 4782  using System;
 4783  using UnityEditor;
 4784  using UnityEngine;
 4785#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
 4786  using Sirenix.Utilities.Editor;
 4787  using Sirenix.OdinInspector.Editor;
 4788  using Sirenix.Utilities;
 4789#endif
 4790
 4791  static partial class FusionEditorGUI {
 4792    internal static T IfOdin<T>(T ifOdin, T ifNotOdin) {
 4793#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
 4794      return ifOdin;
 4795#else
 4796      return ifNotOdin;
 4797#endif
 4798    }
 4799
 4800    internal static UnityEngine.Object ForwardObjectField(Rect position, UnityEngine.Object value, Type objectType, bool
 4801#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
 4802      return SirenixEditorFields.UnityObjectField(position, value, objectType, allowSceneObjects);
 4803#else
 4804      return EditorGUI.ObjectField(position, value, objectType, allowSceneObjects);
 4805#endif
 4806    }
 4807
 4808    internal static UnityEngine.Object ForwardObjectField(Rect position, GUIContent label, UnityEngine.Object value, Typ
 4809#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
 4810      return SirenixEditorFields.UnityObjectField(position, label, value, objectType, allowSceneObjects);
 4811#else
 4812      return EditorGUI.ObjectField(position, label, value, objectType, allowSceneObjects);
 4813#endif
 4814    }
 4815
 4816
 4817    internal static bool ForwardPropertyField(Rect position, SerializedProperty property, GUIContent label, bool include
 4818#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
 4819      if (lastDrawer) {
 4820        switch (property.propertyType) {
 4821          case SerializedPropertyType.ObjectReference: {
 4822              EditorGUI.BeginChangeCheck();
 4823              UnityInternal.ScriptAttributeUtility.GetFieldInfoFromProperty(property, out var fieldType);
 4824              var value = SirenixEditorFields.UnityObjectField(position, label, property.objectReferenceValue, fieldType
 4825              if (EditorGUI.EndChangeCheck()) {
 4826                property.objectReferenceValue = value;
 4827              }
 4828              return false;
 4829            }
 4830
 4831          case SerializedPropertyType.Integer: {
 4832              EditorGUI.BeginChangeCheck();
 4833              var value = SirenixEditorFields.IntField(position, label, property.intValue);
 4834              if (EditorGUI.EndChangeCheck()) {
 4835                property.intValue = value;
 4836              }
 4837              return false;
 4838            }
 4839
 4840          case SerializedPropertyType.Float: {
 4841              EditorGUI.BeginChangeCheck();
 4842              var value = SirenixEditorFields.FloatField(position, label, property.floatValue);
 4843              if (EditorGUI.EndChangeCheck()) {
 4844                property.floatValue = value;
 4845              }
 4846              return false;
 4847            }
 4848
 4849          case SerializedPropertyType.Color: {
 4850              EditorGUI.BeginChangeCheck();
 4851              var value = SirenixEditorFields.ColorField(position, label, property.colorValue);
 4852              if (EditorGUI.EndChangeCheck()) {
 4853                property.colorValue = value;
 4854              }
 4855              return false;
 4856            }
 4857
 4858          case SerializedPropertyType.Vector2: {
 4859              EditorGUI.BeginChangeCheck();
 4860              var value = SirenixEditorFields.Vector2Field(position, label, property.vector2Value);
 4861              if (EditorGUI.EndChangeCheck()) {
 4862                property.vector2Value = value;
 4863              }
 4864              return false;
 4865            }
 4866
 4867          case SerializedPropertyType.Vector3: {
 4868              EditorGUI.BeginChangeCheck();
 4869              var value = SirenixEditorFields.Vector3Field(position, label, property.vector3Value);
 4870              if (EditorGUI.EndChangeCheck()) {
 4871                property.vector3Value = value;
 4872              }
 4873              return false;
 4874            }
 4875
 4876          case SerializedPropertyType.Vector4: {
 4877              EditorGUI.BeginChangeCheck();
 4878              var value = SirenixEditorFields.Vector4Field(position, label, property.vector4Value);
 4879              if (EditorGUI.EndChangeCheck()) {
 4880                property.vector4Value = value;
 4881              }
 4882              return false;
 4883            }
 4884
 4885          case SerializedPropertyType.Quaternion: {
 4886              EditorGUI.BeginChangeCheck();
 4887              var value = SirenixEditorFields.RotationField(position, label, property.quaternionValue, GlobalConfig<Gene
 4888              if (EditorGUI.EndChangeCheck()) {
 4889                property.quaternionValue = value;
 4890              }
 4891              return false;
 4892            }
 4893
 4894          case SerializedPropertyType.String: {
 4895              EditorGUI.BeginChangeCheck();
 4896              var value = SirenixEditorFields.TextField(position, label, property.stringValue);
 4897              if (EditorGUI.EndChangeCheck()) {
 4898                property.stringValue = value;
 4899              }
 4900              return false;
 4901            }
 4902
 4903          case SerializedPropertyType.Enum: {
 4904              UnityInternal.ScriptAttributeUtility.GetFieldInfoFromProperty(property, out var type);
 4905              if (type != null && type.IsEnum) {
 4906                EditorGUI.BeginChangeCheck();
 4907                bool flags = type.GetCustomAttributes(typeof(FlagsAttribute), false).Length > 0;
 4908                Enum value = SirenixEditorFields.EnumDropdown(position, label, (Enum)Enum.ToObject(type, property.intVal
 4909                if (EditorGUI.EndChangeCheck()) {
 4910                  property.intValue = Convert.ToInt32(Convert.ChangeType(value, Enum.GetUnderlyingType(type)));
 4911                }
 4912                return false;
 4913              }
 4914
 4915              break;
 4916            }
 4917
 4918          default:
 4919            break;
 4920        }
 4921      }
 4922#endif
 4923      return EditorGUI.PropertyField(position, property, label, includeChildren);
 4924    }
 4925  }
 4926}
 4927
 4928#endregion
 4929
 4930
 4931#region FusionEditorGUI.Scopes.cs
 4932
 4933namespace Fusion.Editor {
 4934  using System;
 4935  using UnityEditor;
 4936  using UnityEngine;
 4937
 4938  static partial class FusionEditorGUI {
 4939
 4940    public sealed class CustomEditorScope : IDisposable {
 4941
 4942      private SerializedObject serializedObject;
 4943      public  bool             HadChanges { get; private set; }
 4944
 4945      public CustomEditorScope(SerializedObject so) {
 4946        serializedObject = so;
 4947        EditorGUI.BeginChangeCheck();
 4948        so.UpdateIfRequiredOrScript();
 4949        ScriptPropertyField(so);
 4950      }
 4951
 4952      public void Dispose() {
 4953        HadChanges = EditorGUI.EndChangeCheck();
 4954        serializedObject.ApplyModifiedProperties();
 4955      }
 4956    }
 4957
 4958    public struct EnabledScope: IDisposable {
 4959      private readonly bool value;
 4960
 4961      public EnabledScope(bool enabled) {
 4962        value       = GUI.enabled;
 4963        GUI.enabled = enabled;
 4964      }
 4965
 4966      public void Dispose() {
 4967        GUI.enabled = value;
 4968      }
 4969    }
 4970
 4971    public readonly struct BackgroundColorScope : IDisposable {
 4972      private readonly Color value;
 4973
 4974      public BackgroundColorScope(Color color) {
 4975        value               = GUI.backgroundColor;
 4976        GUI.backgroundColor = color;
 4977      }
 4978
 4979      public void Dispose() {
 4980        GUI.backgroundColor = value;
 4981      }
 4982    }
 4983
 4984    public struct ColorScope: IDisposable {
 4985      private readonly Color value;
 4986
 4987      public ColorScope(Color color) {
 4988        value     = GUI.color;
 4989        GUI.color = color;
 4990      }
 4991
 4992      public void Dispose() {
 4993        GUI.color = value;
 4994      }
 4995    }
 4996
 4997    public struct ContentColorScope: IDisposable {
 4998      private readonly Color value;
 4999
 5000      public ContentColorScope(Color color) {
 5001        value            = GUI.contentColor;
 5002        GUI.contentColor = color;
 5003      }
 5004
 5005      public void Dispose() {
 5006        GUI.contentColor = value;
 5007      }
 5008    }
 5009
 5010    public struct FieldWidthScope: IDisposable {
 5011      private readonly float value;
 5012
 5013      public FieldWidthScope(float fieldWidth) {
 5014        value                       = EditorGUIUtility.fieldWidth;
 5015        EditorGUIUtility.fieldWidth = fieldWidth;
 5016      }
 5017
 5018      public void Dispose() {
 5019        EditorGUIUtility.fieldWidth = value;
 5020      }
 5021    }
 5022
 5023    public struct HierarchyModeScope: IDisposable {
 5024      private readonly bool value;
 5025
 5026      public HierarchyModeScope(bool value) {
 5027        this.value                     = EditorGUIUtility.hierarchyMode;
 5028        EditorGUIUtility.hierarchyMode = value;
 5029      }
 5030
 5031      public void Dispose() {
 5032        EditorGUIUtility.hierarchyMode = value;
 5033      }
 5034    }
 5035
 5036    public struct IndentLevelScope: IDisposable {
 5037      private readonly int value;
 5038
 5039      public IndentLevelScope(int indentLevel) {
 5040        value                 = EditorGUI.indentLevel;
 5041        EditorGUI.indentLevel = indentLevel;
 5042      }
 5043
 5044      public void Dispose() {
 5045        EditorGUI.indentLevel = value;
 5046      }
 5047    }
 5048
 5049    public struct LabelWidthScope: IDisposable {
 5050      private readonly float value;
 5051
 5052      public LabelWidthScope(float labelWidth) {
 5053        value                       = EditorGUIUtility.labelWidth;
 5054        EditorGUIUtility.labelWidth = labelWidth;
 5055      }
 5056
 5057      public void Dispose() {
 5058        EditorGUIUtility.labelWidth = value;
 5059      }
 5060    }
 5061
 5062    public struct ShowMixedValueScope: IDisposable {
 5063      private readonly bool value;
 5064
 5065      public ShowMixedValueScope(bool show) {
 5066        value                    = EditorGUI.showMixedValue;
 5067        EditorGUI.showMixedValue = show;
 5068      }
 5069
 5070      public void Dispose() {
 5071        EditorGUI.showMixedValue = value;
 5072      }
 5073    }
 5074
 5075    public struct PropertyScope : IDisposable {
 5076      public PropertyScope(Rect position, GUIContent label, SerializedProperty property) {
 5077        EditorGUI.BeginProperty(position, label, property);
 5078      }
 5079
 5080      public void Dispose() {
 5081        EditorGUI.EndProperty();
 5082      }
 5083    }
 5084
 5085    public readonly struct PropertyScopeWithPrefixLabel : IDisposable {
 5086      private readonly int indent;
 5087
 5088      public PropertyScopeWithPrefixLabel(Rect position, GUIContent label, SerializedProperty property, out Rect indente
 5089        EditorGUI.BeginProperty(position, label, property);
 5090        indentedPosition      = EditorGUI.PrefixLabel(position, label);
 5091        indent                = EditorGUI.indentLevel;
 5092        EditorGUI.indentLevel = 0;
 5093      }
 5094
 5095      public void Dispose() {
 5096        EditorGUI.indentLevel = indent;
 5097        EditorGUI.EndProperty();
 5098      }
 5099    }
 5100
 5101    public readonly struct BoxScope: IDisposable {
 5102
 5103      private readonly int _indent;
 5104
 5105      /// <summary>
 5106      ///if fields include inline help (?) buttons, use indent : 1
 5107      /// </summary>
 5108      public BoxScope(string message, int indent = 0, float space = 0.0f) {
 5109        EditorGUILayout.BeginVertical(FusionEditorSkin.OutlineBoxStyle);
 5110
 5111        if (!string.IsNullOrEmpty(message)) {
 5112          EditorGUILayout.LabelField(message, EditorStyles.boldLabel);
 5113        }
 5114
 5115        if (space > 0.0f) {
 5116          GUILayout.Space(space);
 5117        }
 5118
 5119        _indent = EditorGUI.indentLevel;
 5120        if (indent != 0) {
 5121          EditorGUI.indentLevel += indent;
 5122        }
 5123      }
 5124
 5125      public void Dispose() {
 5126        EditorGUI.indentLevel = _indent;
 5127        EditorGUILayout.EndVertical();
 5128      }
 5129    }
 5130    public struct WarningScope: IDisposable {
 5131      public WarningScope(string message, float space = 0.0f) {
 5132
 5133        var backgroundColor = GUI.backgroundColor;
 5134
 5135        GUI.backgroundColor = FusionEditorSkin.WarningInlineBoxColor;
 5136        EditorGUILayout.BeginVertical(FusionEditorSkin.InlineBoxFullWidthScopeStyle);
 5137        GUI.backgroundColor = backgroundColor;
 5138
 5139        EditorGUILayout.LabelField(new GUIContent(message, FusionEditorSkin.WarningIcon), FusionEditorSkin.RichLabelStyl
 5140        if (space > 0.0f) {
 5141          GUILayout.Space(space);
 5142        }
 5143      }
 5144
 5145      public void Dispose() {
 5146        EditorGUILayout.EndVertical();
 5147      }
 5148    }
 5149
 5150    public struct ErrorScope : IDisposable {
 5151      public ErrorScope(string message, float space = 0.0f) {
 5152        var backgroundColor = GUI.backgroundColor;
 5153
 5154        GUI.backgroundColor = FusionEditorSkin.ErrorInlineBoxColor;
 5155        EditorGUILayout.BeginVertical(FusionEditorSkin.InlineBoxFullWidthScopeStyle);
 5156        GUI.backgroundColor = backgroundColor;
 5157
 5158        EditorGUILayout.LabelField(new GUIContent(message, FusionEditorSkin.ErrorIcon), FusionEditorSkin.RichLabelStyle)
 5159        if (space > 0.0f) {
 5160          GUILayout.Space(space);
 5161        }
 5162      }
 5163
 5164      public void Dispose() {
 5165        EditorGUILayout.EndVertical();
 5166      }
 5167    }
 5168
 5169    public readonly struct GUIContentScope : IDisposable {
 5170
 5171      private readonly string     _text;
 5172      private readonly string     _tooltip;
 5173      private readonly GUIContent _content;
 5174
 5175      public GUIContentScope(GUIContent content) {
 5176        _content = content;
 5177        _text    = content?.text;
 5178        _tooltip = content?.tooltip;
 5179      }
 5180
 5181      public void Dispose() {
 5182        if (_content != null) {
 5183          _content.text    = _text;
 5184          _content.tooltip = _tooltip;
 5185        }
 5186      }
 5187    }
 5188  }
 5189}
 5190
 5191#endregion
 5192
 5193
 5194#region FusionEditorGUI.Utils.cs
 5195
 5196namespace Fusion.Editor {
 5197  using System;
 5198  using System.Collections.Generic;
 5199  using System.Linq;
 5200  using UnityEditor;
 5201  using UnityEngine;
 5202
 5203  static partial class FusionEditorGUI {
 5204    /// <summary>
 5205    /// The name of the script property in Unity objects
 5206    /// </summary>
 5207    public const string ScriptPropertyName = "m_Script";
 5208
 5209    private const int IconHeight = 14;
 5210
 5211    /// <summary>
 5212    /// GUIContent with a single whitespace
 5213    /// </summary>
 5214    public static readonly GUIContent WhitespaceContent = new(" ");
 5215
 5216    internal static Color PrefebOverridenColor => new(1f / 255f, 153f / 255f, 235f / 255f, 0.75f);
 5217
 5218    /// <summary>
 5219    /// Width of the foldout arrow
 5220    /// </summary>
 5221    public static float FoldoutWidth => 16.0f;
 5222
 5223    internal static Rect Decorate(Rect rect, string tooltip, MessageType messageType, bool hasLabel = false, bool drawBo
 5224      if (hasLabel) {
 5225        rect.xMin += EditorGUIUtility.labelWidth;
 5226      }
 5227
 5228      var content  = EditorGUIUtility.TrTextContentWithIcon(string.Empty, tooltip, messageType);
 5229      var iconRect = rect;
 5230      iconRect.width =  Mathf.Min(16, rect.width);
 5231      iconRect.xMin  -= iconRect.width;
 5232
 5233      iconRect.y      += (iconRect.height - IconHeight) / 2;
 5234      iconRect.height =  IconHeight;
 5235
 5236      if (drawButton) {
 5237        using (new EnabledScope(true)) {
 5238          GUI.Label(iconRect, content, GUIStyle.none);
 5239        }
 5240      }
 5241
 5242      //GUI.Label(iconRect, content, new GUIStyle());
 5243
 5244      if (drawBorder) {
 5245        Color borderColor;
 5246        switch (messageType) {
 5247          case MessageType.Warning:
 5248            borderColor = new Color(1.0f, 0.5f, 0.0f);
 5249            break;
 5250          case MessageType.Error:
 5251            borderColor = new Color(1.0f, 0.0f, 0.0f);
 5252            break;
 5253          default:
 5254            borderColor = new Color(1f, 1f, 1f, .0f);
 5255            break;
 5256        }
 5257
 5258        GUI.DrawTexture(rect, Texture2D.whiteTexture, ScaleMode.StretchToFill, false, 0, borderColor, 1.0f, 1.0f);
 5259      }
 5260
 5261      return iconRect;
 5262    }
 5263
 5264    internal static void AppendTooltip(string tooltip, ref GUIContent label) {
 5265      if (!string.IsNullOrEmpty(tooltip)) {
 5266        label = new GUIContent(label);
 5267        if (string.IsNullOrEmpty(label.tooltip)) {
 5268          label.tooltip = tooltip;
 5269        } else {
 5270          label.tooltip += "\n\n" + tooltip;
 5271        }
 5272      }
 5273    }
 5274
 5275    internal static void ScriptPropertyField(Editor editor) {
 5276      ScriptPropertyField(editor.serializedObject);
 5277    }
 5278
 5279    internal static void ScriptPropertyField(SerializedObject obj) {
 5280      var scriptProperty = obj.FindProperty(ScriptPropertyName);
 5281      if (scriptProperty != null) {
 5282        using (new EditorGUI.DisabledScope(true)) {
 5283          EditorGUILayout.PropertyField(scriptProperty);
 5284        }
 5285      }
 5286    }
 5287
 5288    internal static void Overlay(Rect position, string label) {
 5289      GUI.Label(position, label, FusionEditorSkin.OverlayLabelStyle);
 5290    }
 5291
 5292    internal static void Overlay(Rect position, GUIContent label) {
 5293      GUI.Label(position, label, FusionEditorSkin.OverlayLabelStyle);
 5294    }
 5295
 5296    internal static float GetLinesHeight(int count) {
 5297      return count * (EditorGUIUtility.singleLineHeight) + (count - 1) * EditorGUIUtility.standardVerticalSpacing;
 5298    }
 5299
 5300    internal static float GetLinesHeightWithNarrowModeSupport(int count) {
 5301      if (!EditorGUIUtility.wideMode) {
 5302        count++;
 5303      }
 5304      return count * (EditorGUIUtility.singleLineHeight) + (count - 1) * EditorGUIUtility.standardVerticalSpacing;
 5305    }
 5306
 5307    internal static System.Type GetDrawerTypeIncludingWorkarounds(System.Attribute attribute) {
 5308      var drawerType = UnityInternal.ScriptAttributeUtility.GetDrawerTypeForType(attribute.GetType(), false);
 5309      if (drawerType == null) {
 5310        return null;
 5311      }
 5312
 5313      if (drawerType == typeof(PropertyDrawerForArrayWorkaround)) {
 5314        drawerType = PropertyDrawerForArrayWorkaround.GetDrawerType(attribute.GetType());
 5315      }
 5316      return drawerType;
 5317    }
 5318
 5319    internal static void DisplayTypePickerMenu(Rect position, Type[] baseTypes, Action<Type> callback, Func<Type, bool> 
 5320
 5321      var types = new List<Type>();
 5322
 5323      foreach (var baseType in baseTypes) {
 5324        types.AddRange(TypeCache.GetTypesDerivedFrom(baseType).Where(filter));
 5325        if (filter(baseType)) {
 5326          types.Add(baseType);
 5327        }
 5328      }
 5329
 5330      if (baseTypes.Length > 1) {
 5331        types = types.Distinct().ToList();
 5332      }
 5333
 5334      types.Sort((a, b) => string.CompareOrdinal(a.FullName, b.FullName));
 5335
 5336
 5337      List<GUIContent> menuOptions = new List<GUIContent>();
 5338      var actualTypes = new Dictionary<string, System.Type>();
 5339
 5340      menuOptions.Add(new GUIContent(noneOptionLabel));
 5341      actualTypes.Add(noneOptionLabel, null);
 5342
 5343      int selectedIndex = -1;
 5344
 5345      foreach (var ns in types.GroupBy(x => string.IsNullOrEmpty(x.Namespace) ? "[Global Namespace]" : x.Namespace)) {
 5346        foreach (var t in ns) {
 5347          var typeName = t.FullName;
 5348          if (string.IsNullOrEmpty(typeName)) {
 5349            continue;
 5350          }
 5351
 5352          if (!string.IsNullOrEmpty(t.Namespace)) {
 5353            if ((flags & FusionEditorGUIDisplayTypePickerMenuFlags.ShowFullName) == 0) {
 5354              typeName = typeName.Substring(t.Namespace.Length + 1);
 5355            }
 5356          }
 5357
 5358          string path;
 5359          if ((flags & FusionEditorGUIDisplayTypePickerMenuFlags.GroupByNamespace) != 0) {
 5360            path = ns.Key + "/" + typeName;
 5361          } else {
 5362            path = typeName;
 5363          }
 5364
 5365          if (actualTypes.ContainsKey(path)) {
 5366            continue;
 5367          }
 5368
 5369          menuOptions.Add(new GUIContent(path));
 5370          actualTypes.Add(path, t);
 5371
 5372          if (selectedType == t) {
 5373            selectedIndex = menuOptions.Count - 1;
 5374          }
 5375        }
 5376      }
 5377
 5378      EditorUtility.DisplayCustomMenu(position, menuOptions.ToArray(), selectedIndex, (userData, options, selected) => {
 5379        var path = options[selected];
 5380        var newType = ((Dictionary<string, System.Type>)userData)[path];
 5381        callback(newType);
 5382      }, actualTypes);
 5383    }
 5384
 5385
 5386    internal static void DisplayTypePickerMenu(Rect position, Type[] baseTypes, Action<Type> callback, string noneOption
 5387      DisplayTypePickerMenu(position, baseTypes, callback,
 5388        x => (enableAbstract || !x.IsAbstract) && (enableGenericTypeDefinitions || !x.IsGenericTypeDefinition),
 5389        noneOptionLabel: noneOptionLabel,
 5390        flags: flags,
 5391        selectedType: selectedType);
 5392    }
 5393
 5394    internal static void DisplayTypePickerMenu(Rect position, Type baseType, Action<Type> callback, string noneOptionLab
 5395      DisplayTypePickerMenu(position, new [] { baseType }, callback,
 5396        x => (enableAbstract || !x.IsAbstract) && (enableGenericTypeDefinitions || !x.IsGenericTypeDefinition),
 5397        noneOptionLabel: noneOptionLabel,
 5398        flags: flags,
 5399        selectedType: selectedType);
 5400    }
 5401  }
 5402
 5403  /// <summary>
 5404  /// Flags for the <see cref="FusionEditorGUI.DisplayTypePickerMenu(UnityEngine.Rect,System.Type[],System.Action{System
 5405  /// and its overloads.
 5406  /// </summary>
 5407  [Flags]
 5408  public enum FusionEditorGUIDisplayTypePickerMenuFlags {
 5409    /// <summary>
 5410    /// No special flags
 5411    /// </summary>
 5412    None             = 0,
 5413    /// <summary>
 5414    /// Group types by their namespace
 5415    /// </summary>
 5416    GroupByNamespace = 1 << 1,
 5417    /// <summary>
 5418    /// Show the full name of the type including the namespace
 5419    /// </summary>
 5420    ShowFullName     = 1 << 0,
 5421    /// <summary>
 5422    /// The default flags
 5423    /// </summary>
 5424    Default          = GroupByNamespace,
 5425  }
 5426}
 5427
 5428#endregion
 5429
 5430
 5431#region FusionEditorUtility.cs
 5432
 5433namespace Fusion.Editor {
 5434  using UnityEditor;
 5435
 5436  partial class FusionEditorUtility {
 5437    public static void DelayCall(EditorApplication.CallbackFunction callback) {
 5438      FusionEditorLog.Assert(callback.Target == null, "DelayCall callback needs to stateless");
 5439      EditorApplication.delayCall -= callback;
 5440      EditorApplication.delayCall += callback;
 5441    }
 5442  }
 5443}
 5444
 5445#endregion
 5446
 5447
 5448#region FusionGlobalScriptableObjectEditorAttribute.cs
 5449
 5450namespace Fusion.Editor {
 5451  using System;
 5452  using UnityEditor;
 5453
 5454  class FusionGlobalScriptableObjectEditorAttribute : FusionGlobalScriptableObjectSourceAttribute {
 5455    public FusionGlobalScriptableObjectEditorAttribute(Type objectType) : base(objectType) {
 5456    }
 5457
 5458    public override FusionGlobalScriptableObjectLoadResult Load(Type type) {
 5459      var defaultAssetPath = FusionGlobalScriptableObjectUtils.FindDefaultAssetPath(type, fallbackToSearchWithoutLabel: 
 5460      if (string.IsNullOrEmpty(defaultAssetPath)) {
 5461        return default;
 5462      }
 5463
 5464      var result = (FusionGlobalScriptableObject)AssetDatabase.LoadAssetAtPath(defaultAssetPath, type);
 5465      FusionEditorLog.Assert(result);
 5466      return result;
 5467    }
 5468  }
 5469}
 5470
 5471#endregion
 5472
 5473
 5474#region FusionGlobalScriptableObjectUtils.cs
 5475
 5476namespace Fusion.Editor {
 5477  using System;
 5478  using System.Collections.Generic;
 5479  using System.IO;
 5480  using System.Reflection;
 5481  using UnityEditor;
 5482  using UnityEngine;
 5483
 5484  /// <summary>
 5485  /// Utility methods for working with <see cref="FusionGlobalScriptableObject"/>.
 5486  /// </summary>
 5487  public static class FusionGlobalScriptableObjectUtils {
 5488    /// <summary>
 5489    /// The label that is assigned to global assets.
 5490    /// </summary>
 5491    public const string GlobalAssetLabel = "FusionDefaultGlobal";
 5492
 5493    /// <summary>
 5494    /// Calls <see cref="EditorUtility.SetDirty(UnityEngine.Object)"/> on the object.
 5495    /// </summary>
 5496    /// <param name="obj"></param>
 5497    public static void SetDirty(this FusionGlobalScriptableObject obj) {
 5498      EditorUtility.SetDirty(obj);
 5499    }
 5500
 5501    /// <summary>
 5502    /// Locates the asset that is going to be used as a global asset for the given type, that is
 5503    /// an asset marked with the <see cref="GlobalAssetLabel"/> label. If there are multiple such assets,
 5504    /// exception is thrown. If there are no such assets, empty string is returned.
 5505    /// </summary>
 5506    public static string GetGlobalAssetPath<T>() where T : FusionGlobalScriptableObject<T> {
 5507      return FindDefaultAssetPath(typeof(T), fallbackToSearchWithoutLabel: false);
 5508    }
 5509
 5510    /// <summary>
 5511    /// A wrapper around <see cref="GetGlobalAssetPath{T}"/> that returns a value indicating if
 5512    /// it was able to find the asset.
 5513    /// </summary>
 5514    /// <param name="path"></param>
 5515    /// <typeparam name="T"></typeparam>
 5516    /// <returns><see langword="true"/> if the asset was found</returns>
 5517    public static bool TryGetGlobalAssetPath<T>(out string path) where T : FusionGlobalScriptableObject<T> {
 5518      path = FindDefaultAssetPath(typeof(T), fallbackToSearchWithoutLabel: false);
 5519      return !string.IsNullOrEmpty(path);
 5520    }
 5521
 5522    private static FusionGlobalScriptableObjectAttribute GetAttributeOrThrow(Type type) {
 5523      var attribute = type.GetCustomAttribute<FusionGlobalScriptableObjectAttribute>();
 5524      if (attribute == null) {
 5525        throw new InvalidOperationException($"Type {type.FullName} needs to be decorated with {nameof(FusionGlobalScript
 5526      }
 5527
 5528      return attribute;
 5529    }
 5530
 5531    /// <summary>
 5532    /// If the global asset does not exist, creates it based on the type's <see cref="FusionGlobalScriptableObjectAttrib
 5533    /// </summary>
 5534    /// <typeparam name="T"></typeparam>
 5535    /// <returns><see langword="true"/> If the asset already existed.</returns>
 5536    public static bool EnsureAssetExists<T>() where T : FusionGlobalScriptableObject<T> {
 5537      var defaultAssetPath = FindDefaultAssetPath(typeof(T), fallbackToSearchWithoutLabel: true);
 5538      if (!string.IsNullOrEmpty(defaultAssetPath)) {
 5539        // already exists
 5540        return false;
 5541      }
 5542
 5543      // need to create a new asset
 5544      CreateDefaultAsset(typeof(T));
 5545      return true;
 5546    }
 5547
 5548    private static FusionGlobalScriptableObject CreateDefaultAsset(Type type) {
 5549      var attribute = GetAttributeOrThrow(type);
 5550
 5551      var directoryPath = Path.GetDirectoryName(attribute.DefaultPath);
 5552      if (!string.IsNullOrEmpty(directoryPath) && !Directory.Exists(directoryPath)) {
 5553        Directory.CreateDirectory(directoryPath);
 5554        AssetDatabase.Refresh();
 5555      }
 5556
 5557      if (File.Exists(attribute.DefaultPath)) {
 5558        throw new InvalidOperationException($"Asset file already exists at '{attribute.DefaultPath}'");
 5559      }
 5560
 5561      // is this a regular asset?
 5562      if (attribute.DefaultPath.EndsWith(".asset", StringComparison.OrdinalIgnoreCase)) {
 5563        var instance = (FusionGlobalScriptableObject)ScriptableObject.CreateInstance(type);
 5564
 5565        AssetDatabase.CreateAsset(instance, attribute.DefaultPath);
 5566        AssetDatabase.SaveAssets();
 5567
 5568        SetGlobal(instance);
 5569
 5570        EditorUtility.SetDirty(instance);
 5571        AssetDatabase.SaveAssets();
 5572        AssetDatabase.Refresh();
 5573
 5574        FusionEditorLog.TraceImport($"Created new global {type.Name} instance at {attribute.DefaultPath}");
 5575
 5576        return instance;
 5577      } else {
 5578        string defaultContents = null;
 5579        if (!string.IsNullOrEmpty(attribute.DefaultContentsGeneratorMethod)) {
 5580          var method = type.GetMethod(attribute.DefaultContentsGeneratorMethod, BindingFlags.Static | BindingFlags.NonPu
 5581          if (method == null) {
 5582            throw new InvalidOperationException($"Generator method '{attribute.DefaultContentsGeneratorMethod}' not foun
 5583          }
 5584          defaultContents = (string)method.Invoke(null, null);
 5585        }
 5586
 5587        if (defaultContents == null) {
 5588          defaultContents = attribute.DefaultContents;
 5589        }
 5590
 5591        File.WriteAllText(attribute.DefaultPath, defaultContents ?? string.Empty);
 5592        AssetDatabase.ImportAsset(attribute.DefaultPath, ImportAssetOptions.ForceUpdate);
 5593
 5594        var instance = (FusionGlobalScriptableObject)AssetDatabase.LoadAssetAtPath(attribute.DefaultPath, type);
 5595        if (!instance) {
 5596          throw new InvalidOperationException($"Failed to load a newly created asset at '{attribute.DefaultPath}'");
 5597        }
 5598
 5599        SetGlobal(instance);
 5600        FusionEditorLog.TraceImport($"Created new global {type.Name} instance at {attribute.DefaultPath}");
 5601        return instance;
 5602      }
 5603    }
 5604
 5605    private static bool IsDefault(this FusionGlobalScriptableObject obj) {
 5606      return Array.IndexOf(AssetDatabase.GetLabels(obj), GlobalAssetLabel) >= 0;
 5607    }
 5608
 5609    private static bool SetGlobal(FusionGlobalScriptableObject obj) {
 5610      var labels = AssetDatabase.GetLabels(obj);
 5611      if (Array.IndexOf(labels, GlobalAssetLabel) >= 0) {
 5612        return false;
 5613      }
 5614
 5615      Array.Resize(ref labels, labels.Length + 1);
 5616      labels[^1] = GlobalAssetLabel;
 5617      AssetDatabase.SetLabels(obj, labels);
 5618      return true;
 5619    }
 5620
 5621    private static List<(FusionGlobalScriptableObject, bool)> s_cache;
 5622
 5623    internal static void CreateFindDefaultAssetPathCache() {
 5624      s_cache = new List<(FusionGlobalScriptableObject, bool)>();
 5625      foreach (var it in AssetDatabaseUtils.IterateAssets<FusionGlobalScriptableObject>()) {
 5626        var asset = it.pptrValue as FusionGlobalScriptableObject;
 5627        if (asset == null) {
 5628          continue;
 5629        }
 5630
 5631        var hasLabel = AssetDatabaseUtils.HasLabel(asset, GlobalAssetLabel);
 5632        s_cache.Add((asset, hasLabel));
 5633      }
 5634    }
 5635
 5636    internal static void ClearFindDefaultAssetPathCache() {
 5637      s_cache = null;
 5638    }
 5639
 5640    internal static string FindDefaultAssetPath(Type type, bool fallbackToSearchWithoutLabel = false) {
 5641      var list = new List<string>();
 5642
 5643      if (s_cache != null) {
 5644        foreach (var (asset, hasLabel) in s_cache) {
 5645          if (!type.IsInstanceOfType(asset)) {
 5646            continue;
 5647          }
 5648
 5649          if (!hasLabel && !fallbackToSearchWithoutLabel) {
 5650            continue;
 5651          }
 5652
 5653          var assetPath = AssetDatabase.GetAssetPath(asset);
 5654          Assert.Check(!string.IsNullOrEmpty(assetPath));
 5655          list.Add(assetPath);
 5656        }
 5657      } else {
 5658        var enumerator = AssetDatabaseUtils.IterateAssets(type: type, label: fallbackToSearchWithoutLabel ? null : Globa
 5659        foreach (var asset in enumerator) {
 5660          var path = AssetDatabase.GUIDToAssetPath(asset.guid);
 5661          FusionEditorLog.Assert(!string.IsNullOrEmpty(path));
 5662          list.Add(path);
 5663        }
 5664      }
 5665
 5666      if (list.Count == 0) {
 5667        return string.Empty;
 5668      }
 5669
 5670      if (fallbackToSearchWithoutLabel) {
 5671        var found = list.FindIndex(x => AssetDatabaseUtils.HasLabel(x, GlobalAssetLabel));
 5672        if (found >= 0) {
 5673          // carry on as if the search was without fallback in the first place
 5674          list.RemoveAll(x => !AssetDatabaseUtils.HasLabel(x, GlobalAssetLabel));
 5675          fallbackToSearchWithoutLabel = false;
 5676          FusionEditorLog.Assert(list.Count >= 1);
 5677        }
 5678      }
 5679
 5680      if (list.Count == 1) {
 5681        if (fallbackToSearchWithoutLabel) {
 5682          AssetDatabaseUtils.SetLabel(list[0], GlobalAssetLabel, true);
 5683          EditorUtility.SetDirty(AssetDatabase.LoadMainAssetAtPath(list[0]));
 5684          FusionEditorLog.Log($"Set '{list[0]}' as the default asset for '{type.Name}'");
 5685        }
 5686
 5687        return list[0];
 5688      }
 5689
 5690      if (fallbackToSearchWithoutLabel) {
 5691        throw new InvalidOperationException($"There are no assets of type '{type.Name}' with {GlobalAssetLabel}, but the
 5692      } else {
 5693        throw new InvalidOperationException($"There are multiple assets of type '{type.Name}' marked as default: '{strin
 5694      }
 5695    }
 5696
 5697    /// <summary>
 5698    /// Attempts to import the global asset for the given type.
 5699    /// </summary>
 5700    /// <typeparam name="T"></typeparam>
 5701    /// <returns><see langword="true"/> if the asset was found and reimported</returns>
 5702    public static bool TryImportGlobal<T>() where T : FusionGlobalScriptableObject<T> {
 5703      var globalPath = GetGlobalAssetPath<T>();
 5704      if (string.IsNullOrEmpty(globalPath)) {
 5705        return false;
 5706      }
 5707      AssetDatabase.ImportAsset(globalPath);
 5708      return true;
 5709    }
 5710  }
 5711}
 5712
 5713#endregion
 5714
 5715
 5716#region FusionGrid.cs
 5717
 5718namespace Fusion.Editor {
 5719  using System;
 5720  using System.Collections.Generic;
 5721  using System.Linq;
 5722  using System.Linq.Expressions;
 5723  using UnityEditor;
 5724  using UnityEditor.IMGUI.Controls;
 5725  using UnityEngine;
 5726  using Object = UnityEngine.Object;
 5727
 5728  [Serializable]
 5729  class FusionGridState : TreeViewState {
 5730    public MultiColumnHeaderState HeaderState;
 5731    public bool                   SyncSelection;
 5732  }
 5733
 5734  class FusionGridItem : TreeViewItem {
 5735    public virtual Object TargetObject => null;
 5736  }
 5737
 5738  abstract class FusionGrid<TItem> : FusionGrid<TItem, FusionGridState>
 5739    where TItem : FusionGridItem {
 5740  }
 5741
 5742  [Serializable]
 5743  abstract class FusionGrid<TItem, TState>
 5744    where TState : FusionGridState, new()
 5745    where TItem : FusionGridItem
 5746  {
 5747    [SerializeField] public bool   HasValidState;
 5748    [SerializeField] public TState State;
 5749    [SerializeField] public float  UpdatePeriod = 1.0f;
 5750
 5751    class GUIState {
 5752      public InternalTreeView  TreeView;
 5753      public MultiColumnHeader MultiColumnHeader;
 5754      public SearchField       SearchField;
 5755    }
 5756
 5757    [NonSerialized] private Lazy<GUIState> _gui;
 5758    [NonSerialized] private Lazy<Column[]> _columns;
 5759    [NonSerialized] private float          _nextUpdateTime;
 5760    [NonSerialized] private int            _lastContentHash;
 5761
 5762    public virtual int GetContentHash() {
 5763      return 0;
 5764    }
 5765
 5766    public FusionGrid() {
 5767      ResetColumns();
 5768      ResetGUI();
 5769    }
 5770
 5771    void ResetColumns() {
 5772      _columns = new Lazy<Column[]>(() => {
 5773        var columns = CreateColumns().ToArray();
 5774        for (int i = 0; i < columns.Length; ++i) {
 5775          ((MultiColumnHeaderState.Column)columns[i]).userData = i;
 5776        }
 5777
 5778        return columns;
 5779      });
 5780    }
 5781
 5782    void ResetGUI() {
 5783      _gui = new Lazy<GUIState>(() => {
 5784
 5785        var result = new GUIState();
 5786
 5787        result.MultiColumnHeader = new MultiColumnHeader(State.HeaderState);
 5788        result.MultiColumnHeader.sortingChanged += _ => result.TreeView.Reload();
 5789        result.MultiColumnHeader.ResizeToFit();
 5790        result.SearchField = new SearchField();
 5791        result.SearchField.downOrUpArrowKeyPressed += () => result.TreeView.SetFocusAndEnsureSelectedItem();
 5792        result.TreeView = new InternalTreeView(this, result.MultiColumnHeader);
 5793
 5794        return result;
 5795      });
 5796    }
 5797
 5798
 5799    public void OnInspectorUpdate() {
 5800      if (!HasValidState) {
 5801        return;
 5802      }
 5803
 5804      if (!_gui.IsValueCreated) {
 5805        return;
 5806      }
 5807
 5808      if (_nextUpdateTime > Time.realtimeSinceStartup) {
 5809        return;
 5810      }
 5811
 5812      _nextUpdateTime = Time.realtimeSinceStartup + UpdatePeriod;
 5813
 5814      var hash = GetContentHash();
 5815      if (_lastContentHash == hash) {
 5816        return;
 5817      }
 5818
 5819      _lastContentHash = hash;
 5820      _gui.Value.TreeView.Reload();
 5821    }
 5822
 5823    public void OnEnable() {
 5824      if (HasValidState) {
 5825        return;
 5826      }
 5827
 5828      var visibleColumns = new List<int>();
 5829      int sortingColumn = -1;
 5830
 5831      for (int i = 0; i < _columns.Value.Length; ++i) {
 5832        var column = _columns.Value[i];
 5833
 5834        if (sortingColumn < 0 && column.initiallySorted) {
 5835          sortingColumn = i;
 5836          column.sortedAscending = column.initiallySortedAscending;
 5837        }
 5838
 5839        if (!column.initiallyVisible) {
 5840          continue;
 5841        }
 5842
 5843        visibleColumns.Add(i);
 5844      }
 5845
 5846      var headerState = new MultiColumnHeaderState(_columns.Value.Cast<MultiColumnHeaderState.Column>().ToArray()) {
 5847        visibleColumns = visibleColumns.ToArray(),
 5848        sortedColumnIndex = sortingColumn,
 5849      };
 5850
 5851      State = new TState() { HeaderState = headerState };
 5852      HasValidState = true;
 5853      ResetGUI();
 5854    }
 5855
 5856    public void OnGUI(Rect rect) {
 5857      _gui.Value.TreeView.OnGUI(rect);
 5858    }
 5859
 5860    public void DrawToolbarReloadButton() {
 5861      if (GUILayout.Button(new GUIContent(FusionEditorSkin.RefreshIcon, "Refresh"), EditorStyles.toolbarButton, GUILayou
 5862        _gui.Value.TreeView.Reload();
 5863      }
 5864    }
 5865
 5866    public void DrawToolbarSyncSelectionButton() {
 5867      EditorGUI.BeginChangeCheck();
 5868      State.SyncSelection = GUILayout.Toggle(State.SyncSelection, "Sync Selection", EditorStyles.toolbarButton);
 5869      if (EditorGUI.EndChangeCheck()) {
 5870        if (State.SyncSelection) {
 5871          _gui.Value.TreeView.SyncSelection();
 5872        }
 5873      }
 5874    }
 5875
 5876    public void DrawToolbarSearchField() {
 5877      _gui.Value.TreeView.searchString = _gui.Value.SearchField.OnToolbarGUI(_gui.Value.TreeView.searchString);
 5878    }
 5879
 5880    public void DrawToolbarResetView() {
 5881      if (GUILayout.Button("Reset View", EditorStyles.toolbarButton, GUILayout.ExpandWidth(false))) {
 5882        HasValidState = false;
 5883        ResetColumns();
 5884      }
 5885    }
 5886
 5887    public void ResetTree() {
 5888      ResetGUI();
 5889    }
 5890
 5891    protected abstract IEnumerable<Column> CreateColumns();
 5892    protected abstract IEnumerable<TItem>  CreateRows();
 5893
 5894    protected virtual GenericMenu CreateContextMenu(TItem item, TreeView treeView) {
 5895      return null;
 5896    }
 5897
 5898    protected static Column MakeSimpleColumn<T>(Expression<Func<TItem, T>> propertyExpression, Column column) {
 5899
 5900      string propertyName;
 5901      if (propertyExpression.Body is MemberExpression memberExpression) {
 5902        propertyName = memberExpression.Member.Name;
 5903      } else {
 5904        throw new ArgumentException("Expression is not a member access expression.");
 5905      }
 5906
 5907      var accessor = propertyExpression.Compile();
 5908      Func<TItem, string> toString = item => $"{accessor(item)}";
 5909
 5910      column.getSearchText ??= toString;
 5911      column.getComparer ??= order => (a, b) => EditorUtility.NaturalCompare(toString(a), toString(b)) * order;
 5912      column.cellGUI ??= (item, rect, selected, focused) => TreeView.DefaultGUI.Label(rect, toString(item), selected, fo
 5913      if (string.IsNullOrEmpty(column.headerContent.text) && string.IsNullOrEmpty(column.headerContent.tooltip)) {
 5914        column.headerContent = new GUIContent(propertyName);
 5915      }
 5916
 5917      return column;
 5918    }
 5919
 5920    public class Column  : MultiColumnHeaderState.Column {
 5921      public Func<TItem, string>             getSearchText;
 5922      public Func<int, Comparison<TItem>>    getComparer;
 5923      public Action<TItem, Rect, bool, bool> cellGUI;
 5924      public bool                            initiallyVisible = true;
 5925      public bool                            initiallySorted;
 5926      public bool                            initiallySortedAscending = true;
 5927
 5928      //
 5929      // [Obsolete("Do not use", true)]
 5930      // public new int userData => throw new NotImplementedException();
 5931    }
 5932
 5933    class InternalTreeView : TreeView {
 5934      public InternalTreeView(FusionGrid<TItem, TState> grid, MultiColumnHeader header) : base(grid.State, header) {
 5935        Grid = grid;
 5936        showAlternatingRowBackgrounds = true;
 5937        this.Reload();
 5938      }
 5939
 5940      public new TState state => (TState)base.state;
 5941
 5942      public FusionGrid<TItem, TState> Grid { get; }
 5943
 5944
 5945      protected override void SelectionChanged(IList<int> selectedIds) {
 5946        base.SelectionChanged(selectedIds);
 5947        if (state.SyncSelection) {
 5948          SyncSelection();
 5949        }
 5950      }
 5951
 5952      protected override void SingleClickedItem(int id) {
 5953        if (state.SyncSelection) {
 5954          var item = (TItem)FindItem(id, rootItem);
 5955          var obj  = item.TargetObject;
 5956          if (obj) {
 5957            EditorGUIUtility.PingObject(obj);
 5958          }
 5959        }
 5960
 5961        base.SingleClickedItem(id);
 5962      }
 5963
 5964      public void SyncSelection() {
 5965        List<Object> selection = new List<Object>();
 5966        foreach (var id in this.state.selectedIDs) {
 5967          if (id == 0) {
 5968            continue;
 5969          }
 5970          var item = (TItem)FindItem(id, rootItem);
 5971          var obj = item.TargetObject;
 5972          if (obj) {
 5973            selection.Add(obj);
 5974          }
 5975        }
 5976        Selection.objects = selection.ToArray();
 5977      }
 5978
 5979
 5980      private Column GetColumnForIndex(int index) {
 5981        var column = multiColumnHeader.GetColumn(index);
 5982        var ud = column.userData;
 5983        return Grid._columns.Value[ud];
 5984      }
 5985
 5986      protected override TreeViewItem BuildRoot() {
 5987        var allItems = new List<TItem>();
 5988
 5989        var root = new TreeViewItem {
 5990          id          = 0,
 5991          depth       = -1,
 5992          displayName = "Root"
 5993        };
 5994
 5995        foreach (var row in Grid.CreateRows()) {
 5996          allItems.Add(row);
 5997        }
 5998
 5999        SetupParentsAndChildrenFromDepths(root, allItems.Cast<TreeViewItem>().ToList());
 6000        return root;
 6001      }
 6002
 6003      private class ComparisonComparer : IComparer<TItem> {
 6004        public Comparison<TItem> Comparison;
 6005        public int Compare(TItem x, TItem y) => Comparison(x, y);
 6006      }
 6007
 6008      private Comparison<TItem> GetComparision() {
 6009        if (multiColumnHeader.sortedColumnIndex < 0) {
 6010          return null;
 6011        }
 6012        var column = GetColumnForIndex(multiColumnHeader.sortedColumnIndex);
 6013        var isSortedAscending = multiColumnHeader.IsSortedAscending(multiColumnHeader.sortedColumnIndex);
 6014        return column.getComparer(isSortedAscending ? 1 : -1);
 6015      }
 6016
 6017      protected override IList<TreeViewItem> BuildRows(TreeViewItem root) {
 6018        var comparision = GetComparision();
 6019        if (comparision == null) {
 6020          return base.BuildRows(root);
 6021        }
 6022
 6023        // stable sort
 6024        return base.BuildRows(root).OrderBy(x => (TItem)x, new ComparisonComparer() { Comparison = comparision }).ToArra
 6025      }
 6026
 6027      protected override void ContextClickedItem(int id) {
 6028        var item = (TItem)FindItem(id, rootItem);
 6029        if (item == null) {
 6030          return;
 6031        }
 6032
 6033        var menu = Grid.CreateContextMenu(item, this);
 6034        if (menu != null) {
 6035          menu.ShowAsContext();
 6036        }
 6037      }
 6038
 6039      protected override void RowGUI(RowGUIArgs args) {
 6040        for (var i = 0; i < args.GetNumVisibleColumns(); ++i) {
 6041          var cellRect = args.GetCellRect(i);
 6042          CenterRectUsingSingleLineHeight(ref cellRect);
 6043          var item = (TItem)args.item;
 6044          var column = GetColumnForIndex(args.GetColumn(i));
 6045          column.cellGUI?.Invoke(item, cellRect, args.selected, args.focused);
 6046        }
 6047      }
 6048
 6049      protected override bool DoesItemMatchSearch(TreeViewItem item_, string search) {
 6050        var item = item_ as TItem;
 6051        if (item == null) {
 6052          return base.DoesItemMatchSearch(item_, search);
 6053        }
 6054
 6055        var searchParts = (search ?? "").Split(' ', StringSplitOptions.RemoveEmptyEntries);
 6056        if (searchParts.Length == 0) {
 6057          return true;
 6058        }
 6059
 6060        var columns = multiColumnHeader.state.columns;
 6061
 6062        for (var i = 0; i < columns.Length; ++i) {
 6063          if (!multiColumnHeader.IsColumnVisible(i)) {
 6064            continue;
 6065          }
 6066
 6067
 6068          var column = GetColumnForIndex(i);
 6069          var text = column.getSearchText?.Invoke(item);
 6070
 6071          if (text == null) {
 6072            continue;
 6073          }
 6074
 6075          bool columnMatchesSearch = true;
 6076          foreach (var part in searchParts) {
 6077            if (!text.Contains(part, StringComparison.OrdinalIgnoreCase)) {
 6078              columnMatchesSearch = false;
 6079              break;
 6080            }
 6081          }
 6082
 6083          if (columnMatchesSearch) {
 6084            return true;
 6085          }
 6086        }
 6087
 6088        return false;
 6089      }
 6090    }
 6091
 6092    class InternalTreeViewItem : TreeViewItem {
 6093
 6094    }
 6095  }
 6096}
 6097
 6098#endregion
 6099
 6100
 6101#region FusionMonoBehaviourDefaultEditor.cs
 6102
 6103namespace Fusion.Editor {
 6104  using UnityEditor;
 6105
 6106  [CustomEditor(typeof(FusionMonoBehaviour), true)]
 6107  internal class FusionMonoBehaviourDefaultEditor : FusionEditor {
 6108  }
 6109}
 6110
 6111#endregion
 6112
 6113
 6114#region FusionPropertyDrawerMetaAttribute.cs
 6115
 6116namespace Fusion.Editor {
 6117  using System;
 6118
 6119  [AttributeUsage(AttributeTargets.Class)]
 6120  class FusionPropertyDrawerMetaAttribute : Attribute {
 6121    public bool HasFoldout   { get; set; }
 6122    public bool HandlesUnits { get; set; }
 6123  }
 6124}
 6125
 6126#endregion
 6127
 6128
 6129#region FusionScriptableObjectDefaultEditor.cs
 6130
 6131namespace Fusion.Editor {
 6132  using UnityEditor;
 6133
 6134  [CustomEditor(typeof(FusionScriptableObject), true)]
 6135  internal class FusionScriptableObjectDefaultEditor : FusionEditor {
 6136  }
 6137}
 6138
 6139#endregion
 6140
 6141
 6142#region RawDataDrawer.cs
 6143
 6144namespace Fusion.Editor {
 6145  using System;
 6146  using System.Collections.Generic;
 6147  using System.Text;
 6148  using UnityEditor;
 6149  using UnityEngine;
 6150
 6151  struct RawDataDrawer {
 6152    private StringBuilder _builder;
 6153    private GUIContent    _lastValue;
 6154    private int           _lastHash;
 6155
 6156    public void Clear() {
 6157      _builder?.Clear();
 6158      _lastHash = 0;
 6159      _lastValue = GUIContent.none;
 6160    }
 6161
 6162    public bool HasContent => _lastValue != null && _lastValue.text.Length > 0;
 6163
 6164    public unsafe void Refresh<T>(Span<T> data, int maxLength = 2048, bool addSpaces = true) where T : unmanaged {
 6165
 6166      int charactersPerElement = 2 * sizeof(T);
 6167
 6168      int arrayHash = 0;
 6169      int effectiveArraySize;
 6170      {
 6171        int length = 0;
 6172        int i;
 6173        for (i = 0; i < data.Length && length < maxLength; ++i) {
 6174          arrayHash = arrayHash * 31 + data[i].GetHashCode();
 6175          length += charactersPerElement;
 6176          if (addSpaces) {
 6177            length += 1;
 6178          }
 6179        }
 6180
 6181        effectiveArraySize = i;
 6182      }
 6183
 6184      if (_builder == null || arrayHash != _lastHash) {
 6185        var format = "{0:x" + charactersPerElement + "}" + (addSpaces ? " " : "");
 6186
 6187        _builder ??= new StringBuilder();
 6188        _builder.Clear();
 6189
 6190
 6191        for (int i = 0; i < effectiveArraySize; ++i) {
 6192          _builder.AppendFormat(format, data[i]);
 6193        }
 6194
 6195        if (effectiveArraySize < data.Length) {
 6196          _builder.AppendLine("...");
 6197        }
 6198
 6199        _lastHash = arrayHash;
 6200        _lastValue = new GUIContent(_builder.ToString());
 6201      } else {
 6202        Debug.Assert(_lastValue != null);
 6203      }
 6204    }
 6205
 6206    public void Refresh(IList<byte> values, int maxLength = 2048) {
 6207      Assert.Check(values != null);
 6208
 6209      const int charactersPerElement = 2;
 6210      int arraySize = values.Count;
 6211      int arrayHash = 0;
 6212      int effectiveArraySize;
 6213      {
 6214        int length = 0;
 6215        int i;
 6216        for (i = 0; i < arraySize && length < maxLength; ++i) {
 6217          arrayHash = arrayHash * 31 + values[i];
 6218          length += charactersPerElement + 1;
 6219        }
 6220
 6221        effectiveArraySize = i;
 6222      }
 6223
 6224      if (_builder == null || arrayHash != _lastHash) {
 6225        var format = "{0:x" + charactersPerElement + "} ";
 6226
 6227        _builder ??= new StringBuilder();
 6228        _builder.Clear();
 6229
 6230        for (int i = 0; i < effectiveArraySize; ++i) {
 6231          _builder.AppendFormat(format, values[i]);
 6232        }
 6233
 6234        if (effectiveArraySize < arraySize) {
 6235          _builder.AppendLine("...");
 6236        }
 6237
 6238        _lastHash = arrayHash;
 6239        _lastValue = new GUIContent(_builder.ToString());
 6240      } else {
 6241        Debug.Assert(_lastValue != null);
 6242      }
 6243    }
 6244
 6245    public void Refresh(SerializedProperty property, int maxLength = 2048) {
 6246      Assert.Check(property != null);
 6247      Assert.Check(property.isArray);
 6248
 6249      int charactersPerElement;
 6250      switch (property.arrayElementType) {
 6251        case "long":
 6252        case "ulong":
 6253          charactersPerElement = 16;
 6254          break;
 6255        case "int":
 6256        case "uint":
 6257          charactersPerElement = 8;
 6258          break;
 6259        case "short":
 6260        case "ushort":
 6261          charactersPerElement = 4;
 6262          break;
 6263        case "sbyte":
 6264        case "byte":
 6265          charactersPerElement = 2;
 6266          break;
 6267        default:
 6268          throw new NotImplementedException(property.arrayElementType);
 6269      }
 6270
 6271      int arrayHash = 0;
 6272      int effectiveArraySize;
 6273      {
 6274        int length = 0;
 6275        int i;
 6276        for (i = 0; i < property.arraySize && length < maxLength; ++i) {
 6277          arrayHash = arrayHash * 31 + property.GetArrayElementAtIndex(i).longValue.GetHashCode();
 6278          length += charactersPerElement + 1;
 6279        }
 6280
 6281        effectiveArraySize = i;
 6282      }
 6283
 6284      if (_builder == null || arrayHash != _lastHash) {
 6285        var format = "{0:x" + charactersPerElement + "} ";
 6286
 6287        _builder ??= new StringBuilder();
 6288        _builder.Clear();
 6289
 6290        for (int i = 0; i < effectiveArraySize; ++i) {
 6291          _builder.AppendFormat(format, property.GetArrayElementAtIndex(i).longValue);
 6292        }
 6293
 6294        if (effectiveArraySize < property.arraySize) {
 6295          _builder.AppendLine("...");
 6296        }
 6297
 6298        _lastHash = arrayHash;
 6299        _lastValue = new GUIContent(_builder.ToString());
 6300      } else {
 6301        Debug.Assert(_lastValue != null);
 6302      }
 6303    }
 6304
 6305
 6306    public float GetHeight(float width) {
 6307      return FusionEditorSkin.RawDataStyle.Value.CalcHeight(_lastValue ?? GUIContent.none, width);
 6308    }
 6309
 6310    public string Draw(Rect position) => Draw(GUIContent.none, position);
 6311
 6312    public string Draw(GUIContent label, Rect position) {
 6313      var id = GUIUtility.GetControlID(UnityInternal.EditorGUI.DelayedTextFieldHash, FocusType.Keyboard, position);
 6314      return UnityInternal.EditorGUI.DelayedTextFieldInternal(position, id, label, _lastValue.text ?? string.Empty, "012
 6315    }
 6316
 6317    public string DrawLayout() {
 6318      var position = EditorGUILayout.GetControlRect(false, 18f, FusionEditorSkin.RawDataStyle);
 6319      return Draw(position);
 6320    }
 6321  }
 6322}
 6323
 6324#endregion
 6325
 6326
 6327#region ReflectionUtils.cs
 6328
 6329namespace Fusion.Editor {
 6330  using System;
 6331  using System.Collections.Generic;
 6332  using System.Linq;
 6333  using System.Linq.Expressions;
 6334  using System.Reflection;
 6335  using UnityEditor;
 6336
 6337  static partial class ReflectionUtils {
 6338    public const BindingFlags DefaultBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static |
 6339
 6340    public static Type GetUnityLeafType(this Type type) {
 6341      if (type.HasElementType) {
 6342        type = type.GetElementType();
 6343      } else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>)) {
 6344        type = type.GetGenericArguments()[0];
 6345      }
 6346
 6347      return type;
 6348    }
 6349
 6350    public static T CreateMethodDelegate<T>(this Type type, string methodName, BindingFlags flags = DefaultBindingFlags)
 6351      try {
 6352        return CreateMethodDelegateInternal<T>(type, methodName, flags);
 6353      } catch (Exception ex) {
 6354        throw new InvalidOperationException(CreateMethodExceptionMessage<T>(type.Assembly, type.FullName, methodName, fl
 6355      }
 6356    }
 6357
 6358    public static Delegate CreateMethodDelegate(this Type type, string methodName, BindingFlags flags, Type delegateType
 6359      try {
 6360        return CreateMethodDelegateInternal(type, methodName, flags, delegateType);
 6361      } catch (Exception ex) {
 6362        throw new InvalidOperationException(CreateMethodExceptionMessage(type.Assembly, type.FullName, methodName, flags
 6363      }
 6364    }
 6365
 6366    public static T CreateMethodDelegate<T>(Assembly assembly, string typeName, string methodName, BindingFlags flags = 
 6367      try {
 6368        var type = assembly.GetType(typeName, true);
 6369        return CreateMethodDelegateInternal<T>(type, methodName, flags);
 6370      } catch (Exception ex) {
 6371        throw new InvalidOperationException(CreateMethodExceptionMessage<T>(assembly, typeName, methodName, flags), ex);
 6372      }
 6373    }
 6374
 6375    public static Delegate CreateMethodDelegate(Assembly assembly, string typeName, string methodName, BindingFlags flag
 6376      try {
 6377        var type = assembly.GetType(typeName, true);
 6378        return CreateMethodDelegateInternal(type, methodName, flags, delegateType);
 6379      } catch (Exception ex) {
 6380        throw new InvalidOperationException(CreateMethodExceptionMessage(assembly, typeName, methodName, flags, delegate
 6381      }
 6382    }
 6383
 6384    internal static T CreateMethodDelegate<T>(this Type type, string methodName, BindingFlags flags, Type delegateType, 
 6385      try {
 6386        delegateType ??= typeof(T);
 6387
 6388
 6389        var method = GetMethodOrThrow(type, methodName, flags, delegateType, fallbackSwizzles, out var swizzle);
 6390        if (swizzle == null && typeof(T) == delegateType) {
 6391          return (T)Delegate.CreateDelegate(typeof(T), method);
 6392        }
 6393
 6394        var delegateParameters = typeof(T).GetMethod("Invoke").GetParameters();
 6395        var parameters         = new List<ParameterExpression>();
 6396
 6397        for (var i = 0; i < delegateParameters.Length; ++i) {
 6398          parameters.Add(Expression.Parameter(delegateParameters[i].ParameterType, $"param_{i}"));
 6399        }
 6400
 6401        var convertedParameters = new List<Expression>();
 6402        {
 6403          var methodParameters = method.GetParameters();
 6404          if (swizzle == null) {
 6405            for (int i = 0, j = method.IsStatic ? 0 : 1; i < methodParameters.Length; ++i, ++j) {
 6406              convertedParameters.Add(Expression.Convert(parameters[j], methodParameters[i].ParameterType));
 6407            }
 6408          } else {
 6409            foreach (var converter in swizzle.Converters) {
 6410              convertedParameters.Add(Expression.Invoke(converter, parameters));
 6411            }
 6412          }
 6413        }
 6414
 6415
 6416        MethodCallExpression callExpression;
 6417        if (method.IsStatic) {
 6418          callExpression = Expression.Call(method, convertedParameters);
 6419        } else {
 6420          var instance = Expression.Convert(parameters[0], method.DeclaringType);
 6421          callExpression = Expression.Call(instance, method, convertedParameters);
 6422        }
 6423
 6424        var l   = Expression.Lambda(typeof(T), callExpression, parameters);
 6425        var del = l.Compile();
 6426        return (T)del;
 6427      } catch (Exception ex) {
 6428        throw new InvalidOperationException(CreateMethodExceptionMessage<T>(type.Assembly, type.FullName, methodName, fl
 6429      }
 6430    }
 6431
 6432    /// <summary>
 6433    ///   Returns the first found member of the given name. Includes private members.
 6434    /// </summary>
 6435    public static MemberInfo GetMemberIncludingBaseTypes(this Type type, string memberName, BindingFlags flags = Default
 6436      var members = type.GetMember(memberName, flags);
 6437      if (members.Length > 0) {
 6438        return members[0];
 6439      }
 6440
 6441      type = type.BaseType;
 6442
 6443      // loop as long as we have a parent class to search.
 6444      while (type != null) {
 6445        // No point recursing into the abstracts.
 6446        if (type == stopAtType) {
 6447          break;
 6448        }
 6449
 6450        members = type.GetMember(memberName, flags);
 6451        if (members.Length > 0) {
 6452          return members[0];
 6453        }
 6454
 6455        type = type.BaseType;
 6456      }
 6457
 6458      return null;
 6459    }
 6460
 6461    /// <summary>
 6462    ///   Normal reflection GetField() won't find private fields in parents (only will find protected). So this recurses
 6463    ///   hard to find privates.
 6464    ///   This is needed since Unity serialization does find inherited privates.
 6465    /// </summary>
 6466    public static FieldInfo GetFieldIncludingBaseTypes(this Type type, string fieldName, BindingFlags flags = DefaultBin
 6467      var field = type.GetField(fieldName, flags);
 6468      if (field != null) {
 6469        return field;
 6470      }
 6471
 6472      type = type.BaseType;
 6473
 6474      // loop as long as we have a parent class to search.
 6475      while (type != null) {
 6476        // No point recursing into the abstracts.
 6477        if (type == stopAtType) {
 6478          break;
 6479        }
 6480
 6481        field = type.GetField(fieldName, flags);
 6482        if (field != null) {
 6483          return field;
 6484        }
 6485
 6486        type = type.BaseType;
 6487      }
 6488
 6489      return null;
 6490    }
 6491
 6492    public static FieldInfo GetFieldOrThrow(this Type type, string fieldName, BindingFlags flags = DefaultBindingFlags) 
 6493      var field = type.GetField(fieldName, flags);
 6494      if (field == null) {
 6495        throw new ArgumentOutOfRangeException(nameof(fieldName), CreateFieldExceptionMessage(type.Assembly, type.FullNam
 6496      }
 6497
 6498      return field;
 6499    }
 6500
 6501    public static FieldInfo GetFieldOrThrow<T>(this Type type, string fieldName, BindingFlags flags = DefaultBindingFlag
 6502      return GetFieldOrThrow(type, fieldName, typeof(T), flags);
 6503    }
 6504
 6505    public static FieldInfo GetFieldOrThrow(this Type type, string fieldName, Type fieldType, BindingFlags flags = Defau
 6506      var field = type.GetField(fieldName, flags);
 6507      if (field == null) {
 6508        throw new ArgumentOutOfRangeException(nameof(fieldName), CreateFieldExceptionMessage(type.Assembly, type.FullNam
 6509      }
 6510
 6511      if (fieldType != null) {
 6512        if (field.FieldType != fieldType) {
 6513          throw new InvalidProgramException($"Field {type.FullName}.{fieldName} is of type {field.FieldType}, not expect
 6514        }
 6515      }
 6516
 6517      return field;
 6518    }
 6519
 6520    public static PropertyInfo GetPropertyOrThrow<T>(this Type type, string propertyName, BindingFlags flags = DefaultBi
 6521      return GetPropertyOrThrow(type, propertyName, typeof(T), flags);
 6522    }
 6523
 6524    public static PropertyInfo GetPropertyOrThrow(this Type type, string propertyName, Type propertyType, BindingFlags f
 6525      var property = type.GetProperty(propertyName, flags);
 6526      if (property == null) {
 6527        throw new ArgumentOutOfRangeException(nameof(propertyName), CreateFieldExceptionMessage(type.Assembly, type.Full
 6528      }
 6529
 6530      if (property.PropertyType != propertyType) {
 6531        throw new InvalidProgramException($"Property {type.FullName}.{propertyName} is of type {property.PropertyType}, 
 6532      }
 6533
 6534      return property;
 6535    }
 6536
 6537    public static PropertyInfo GetPropertyOrThrow(this Type type, string propertyName, BindingFlags flags = DefaultBindi
 6538      var property = type.GetProperty(propertyName, flags);
 6539      if (property == null) {
 6540        throw new ArgumentOutOfRangeException(nameof(propertyName), CreateFieldExceptionMessage(type.Assembly, type.Full
 6541      }
 6542
 6543      return property;
 6544    }
 6545
 6546    public static MethodInfo GetMethodOrThrow(this Type type, string methodName, BindingFlags flags = DefaultBindingFlag
 6547      var method = type.GetMethod(methodName, flags);
 6548      if (method == null) {
 6549        throw new ArgumentOutOfRangeException(nameof(methodName), CreateFieldExceptionMessage(type.Assembly, type.FullNa
 6550      }
 6551
 6552      return method;
 6553    }
 6554
 6555    public static ConstructorInfo GetConstructorInfoOrThrow(this Type type, Type[] types, BindingFlags flags = DefaultBi
 6556      var constructor = type.GetConstructor(flags, null, types, null);
 6557      if (constructor == null) {
 6558        throw new ArgumentOutOfRangeException(nameof(types), CreateConstructorExceptionMessage(type.Assembly, type.FullN
 6559      }
 6560
 6561      return constructor;
 6562    }
 6563
 6564    public static Type GetNestedTypeOrThrow(this Type type, string name, BindingFlags flags) {
 6565      var result = type.GetNestedType(name, flags);
 6566      if (result == null) {
 6567        throw new ArgumentOutOfRangeException(nameof(name), CreateFieldExceptionMessage(type.Assembly, type.FullName, na
 6568      }
 6569
 6570      return result;
 6571    }
 6572
 6573    public static Func<object, object> CreateGetter(this Type type, string memberName, BindingFlags flags = DefaultBindi
 6574      return CreateGetter<object>(type, memberName, flags);
 6575    }
 6576
 6577    public static Func<object, T> CreateGetter<T>(this Type type, string memberName, BindingFlags flags = DefaultBinding
 6578      var candidates = type.GetMembers(flags).Where(x => x.Name == memberName)
 6579       .ToList();
 6580
 6581      if (candidates.Count > 1) {
 6582        throw new InvalidOperationException($"Multiple members with name {memberName} found in type {type.FullName}");
 6583      }
 6584      if (candidates.Count == 0) {
 6585        throw new ArgumentOutOfRangeException(nameof(memberName),$"No members with name {memberName} found in type {type
 6586      }
 6587
 6588      var  candidate = candidates[0];
 6589      bool isStatic  = false;
 6590      switch (candidate) {
 6591        case FieldInfo field:
 6592          isStatic = field.IsStatic;
 6593          break;
 6594        case PropertyInfo property:
 6595          isStatic = property.GetMethod.IsStatic;
 6596          break;
 6597        case MethodInfo method:
 6598          isStatic = method.IsStatic;
 6599          break;
 6600      }
 6601
 6602      if (isStatic) {
 6603        var getter = CreateStaticAccessorInternal<T>(candidate).GetValue;
 6604        return _ => getter();
 6605      } else {
 6606        return CreateAccessorInternal<T>(candidate).GetValue;
 6607      }
 6608    }
 6609
 6610    public static InstanceAccessor<object> CreateFieldAccessor(this Type type, string fieldName, Type expectedFieldType 
 6611      return CreateFieldAccessor<object>(type, fieldName, expectedFieldType);
 6612    }
 6613
 6614    public static InstanceAccessor<FieldType> CreateFieldAccessor<FieldType>(this Type type, string fieldName, Type expe
 6615      var field = type.GetFieldOrThrow(fieldName, expectedFieldType ?? typeof(FieldType), BindingFlags.Instance | Bindin
 6616      return CreateAccessorInternal<FieldType>(field);
 6617    }
 6618
 6619    public static StaticAccessor<object> CreateStaticFieldAccessor(this Type type, string fieldName, Type expectedFieldT
 6620      return CreateStaticFieldAccessor<object>(type, fieldName, expectedFieldType);
 6621    }
 6622
 6623    public static StaticAccessor<FieldType> CreateStaticFieldAccessor<FieldType>(this Type type, string fieldName, Type 
 6624      var field = type.GetFieldOrThrow(fieldName, expectedFieldType ?? typeof(FieldType), BindingFlags.Static | BindingF
 6625      return CreateStaticAccessorInternal<FieldType>(field);
 6626    }
 6627
 6628    public static InstanceAccessor<PropertyType> CreatePropertyAccessor<PropertyType>(this Type type, string fieldName, 
 6629      var field = type.GetPropertyOrThrow(fieldName, expectedPropertyType ?? typeof(PropertyType), BindingFlags.Instance
 6630      return CreateAccessorInternal<PropertyType>(field);
 6631    }
 6632
 6633    public static StaticAccessor<object> CreateStaticPropertyAccessor(this Type type, string fieldName, Type expectedFie
 6634      return CreateStaticPropertyAccessor<object>(type, fieldName, expectedFieldType);
 6635    }
 6636
 6637    public static StaticAccessor<FieldType> CreateStaticPropertyAccessor<FieldType>(this Type type, string fieldName, Ty
 6638      var field = type.GetPropertyOrThrow(fieldName, expectedFieldType ?? typeof(FieldType), BindingFlags.Static | Bindi
 6639      return CreateStaticAccessorInternal<FieldType>(field);
 6640    }
 6641
 6642    private static string CreateMethodExceptionMessage<T>(Assembly assembly, string typeName, string methodName, Binding
 6643      return CreateMethodExceptionMessage(assembly, typeName, methodName, flags, typeof(T));
 6644    }
 6645
 6646    private static string CreateMethodExceptionMessage(Assembly assembly, string typeName, string methodName, BindingFla
 6647      return $"{assembly.FullName}.{typeName}.{methodName} with flags: {flags} and type: {delegateType}";
 6648    }
 6649
 6650    private static string CreateFieldExceptionMessage(Assembly assembly, string typeName, string fieldName, BindingFlags
 6651      return $"{assembly.FullName}.{typeName}.{fieldName} with flags: {flags}";
 6652    }
 6653
 6654    private static string CreateConstructorExceptionMessage(Assembly assembly, string typeName, BindingFlags flags) {
 6655      return $"{assembly.FullName}.{typeName}() with flags: {flags}";
 6656    }
 6657
 6658    private static string CreateConstructorExceptionMessage(Assembly assembly, string typeName, Type[] types, BindingFla
 6659      return $"{assembly.FullName}.{typeName}({string.Join(", ", types.Select(x => x.FullName))}) with flags: {flags}";
 6660    }
 6661
 6662    private static T CreateMethodDelegateInternal<T>(this Type type, string name, BindingFlags flags) where T : Delegate
 6663      return (T)CreateMethodDelegateInternal(type, name, flags, typeof(T));
 6664    }
 6665
 6666    private static Delegate CreateMethodDelegateInternal(this Type type, string name, BindingFlags flags, Type delegateT
 6667      var method = GetMethodOrThrow(type, name, flags, delegateType);
 6668      return Delegate.CreateDelegate(delegateType, null, method);
 6669    }
 6670
 6671    private static MethodInfo GetMethodOrThrow(Type type, string name, BindingFlags flags, Type delegateType) {
 6672      return GetMethodOrThrow(type, name, flags, delegateType, Array.Empty<DelegateSwizzle>(), out _);
 6673    }
 6674
 6675    private static MethodInfo FindMethod(Type type, string name, BindingFlags flags, Type returnType, params Type[] para
 6676      var method = type.GetMethod(name, flags, null, parameters, null);
 6677
 6678      if (method == null) {
 6679        return null;
 6680      }
 6681
 6682      if (method.ReturnType != returnType) {
 6683        return null;
 6684      }
 6685
 6686      return method;
 6687    }
 6688
 6689    private static ConstructorInfo GetConstructorOrThrow(Type type, BindingFlags flags, Type delegateType, DelegateSwizz
 6690      var delegateMethod = delegateType.GetMethod("Invoke");
 6691
 6692      var allDelegateParameters = delegateMethod.GetParameters().Select(x => x.ParameterType).ToArray();
 6693
 6694      var constructor = type.GetConstructor(flags, null, allDelegateParameters, null);
 6695      if (constructor != null) {
 6696        firstMatchingSwizzle = null;
 6697        return constructor;
 6698      }
 6699
 6700      if (swizzles != null) {
 6701        foreach (var swizzle in swizzles) {
 6702          var swizzled = swizzle.Types;
 6703          constructor = type.GetConstructor(flags, null, swizzled, null);
 6704          if (constructor != null) {
 6705            firstMatchingSwizzle = swizzle;
 6706            return constructor;
 6707          }
 6708        }
 6709      }
 6710
 6711      var constructors = type.GetConstructors(flags);
 6712      throw new ArgumentOutOfRangeException(nameof(delegateType), $"No matching constructor found for {type}, " +
 6713        $"signature \"{delegateType}\", " +
 6714        $"flags \"{flags}\" and " +
 6715        $"params: {string.Join(", ", allDelegateParameters.Select(x => x.FullName))}" +
 6716        $", candidates are\n: {string.Join("\n", constructors.Select(x => x.ToString()))}");
 6717    }
 6718
 6719    private static MethodInfo GetMethodOrThrow(Type type, string name, BindingFlags flags, Type delegateType, DelegateSw
 6720      var delegateMethod = delegateType.GetMethod("Invoke");
 6721
 6722      var allDelegateParameters = delegateMethod.GetParameters().Select(x => x.ParameterType).ToArray();
 6723
 6724      var method = FindMethod(type, name, flags, delegateMethod.ReturnType, flags.HasFlag(BindingFlags.Static) ? allDele
 6725      if (method != null) {
 6726        firstMatchingSwizzle = null;
 6727        return method;
 6728      }
 6729
 6730      if (swizzles != null) {
 6731        foreach (var swizzle in swizzles) {
 6732          var swizzled = swizzle.Types;
 6733          if (!flags.HasFlag(BindingFlags.Static) && swizzled[0] != type) {
 6734            throw new InvalidOperationException();
 6735          }
 6736
 6737          method = FindMethod(type, name, flags, delegateMethod.ReturnType, flags.HasFlag(BindingFlags.Static) ? swizzle
 6738          if (method != null) {
 6739            firstMatchingSwizzle = swizzle;
 6740            return method;
 6741          }
 6742        }
 6743      }
 6744
 6745      var methods = type.GetMethods(flags);
 6746      throw new ArgumentOutOfRangeException(nameof(name), $"No method found matching name \"{name}\", " +
 6747        $"signature \"{delegateType}\", " +
 6748        $"flags \"{flags}\" and " +
 6749        $"params: {string.Join(", ", allDelegateParameters.Select(x => x.FullName))}" +
 6750        $", candidates are\n: {string.Join("\n", methods.Select(x => x.ToString()))}");
 6751    }
 6752
 6753    public static bool IsArrayOrList(this Type listType) {
 6754      if (listType.IsArray) {
 6755        return true;
 6756      }
 6757
 6758      if (listType.IsGenericType && listType.GetGenericTypeDefinition() == typeof(List<>)) {
 6759        return true;
 6760      }
 6761
 6762      return false;
 6763    }
 6764
 6765    public static Type GetArrayOrListElementType(this Type listType) {
 6766      if (listType.IsArray) {
 6767        return listType.GetElementType();
 6768      }
 6769
 6770      if (listType.IsGenericType && listType.GetGenericTypeDefinition() == typeof(List<>)) {
 6771        return listType.GetGenericArguments()[0];
 6772      }
 6773
 6774      return null;
 6775    }
 6776
 6777    public static Type MakeFuncType(params Type[] types) {
 6778      return GetFuncType(types.Length).MakeGenericType(types);
 6779    }
 6780
 6781    private static Type GetFuncType(int argumentCount) {
 6782      switch (argumentCount) {
 6783        case 1:  return typeof(Func<>);
 6784        case 2:  return typeof(Func<,>);
 6785        case 3:  return typeof(Func<,,>);
 6786        case 4:  return typeof(Func<,,,>);
 6787        case 5:  return typeof(Func<,,,,>);
 6788        case 6:  return typeof(Func<,,,,,>);
 6789        default: throw new ArgumentOutOfRangeException(nameof(argumentCount));
 6790      }
 6791    }
 6792
 6793    public static Type MakeActionType(params Type[] types) {
 6794      if (types.Length == 0) {
 6795        return typeof(Action);
 6796      }
 6797
 6798      return GetActionType(types.Length).MakeGenericType(types);
 6799    }
 6800
 6801    private static Type GetActionType(int argumentCount) {
 6802      switch (argumentCount) {
 6803        case 1:  return typeof(Action<>);
 6804        case 2:  return typeof(Action<,>);
 6805        case 3:  return typeof(Action<,,>);
 6806        case 4:  return typeof(Action<,,,>);
 6807        case 5:  return typeof(Action<,,,,>);
 6808        case 6:  return typeof(Action<,,,,,>);
 6809        default: throw new ArgumentOutOfRangeException(nameof(argumentCount));
 6810      }
 6811    }
 6812
 6813    private static StaticAccessor<T> CreateStaticAccessorInternal<T>(MemberInfo member) {
 6814      try {
 6815        var valueParameter = Expression.Parameter(typeof(T), "value");
 6816        var canWrite       = true;
 6817
 6818        UnaryExpression  valueExpression;
 6819        Expression memberExpression;
 6820
 6821        switch (member) {
 6822          case PropertyInfo property:
 6823            valueExpression  = Expression.Convert(valueParameter, property.PropertyType);
 6824            memberExpression = Expression.Property(null, property);
 6825            canWrite         = property.CanWrite;
 6826            break;
 6827          case FieldInfo field:
 6828            valueExpression  = Expression.Convert(valueParameter, field.FieldType);
 6829            memberExpression = Expression.Field(null, field);
 6830            canWrite         = field.IsInitOnly == false;
 6831            break;
 6832          case MethodInfo method when method.GetParameters().Length == 0:
 6833            valueExpression  = null;
 6834            memberExpression = Expression.Call(method);
 6835            canWrite         = false;
 6836            break;
 6837          default:
 6838            throw new InvalidOperationException($"Unsupported member type {member.GetType().Name}");
 6839        }
 6840
 6841        Func<T> getter;
 6842        var     getExpression = Expression.Convert(memberExpression, typeof(T));
 6843        var     getLambda     = Expression.Lambda<Func<T>>(getExpression);
 6844        getter = getLambda.Compile();
 6845
 6846        Action<T> setter = null;
 6847        if (canWrite) {
 6848          var setExpression = Expression.Assign(memberExpression, valueExpression);
 6849          var setLambda     = Expression.Lambda<Action<T>>(setExpression, valueParameter);
 6850          setter = setLambda.Compile();
 6851        }
 6852
 6853        return new StaticAccessor<T> {
 6854          GetValue = getter,
 6855          SetValue = setter
 6856        };
 6857      } catch (Exception ex) {
 6858        throw new InvalidOperationException($"Failed to create accessor for {member.DeclaringType}.{member.Name}", ex);
 6859      }
 6860    }
 6861
 6862    private static InstanceAccessor<T> CreateAccessorInternal<T>(MemberInfo member) {
 6863      try {
 6864        var instanceParameter  = Expression.Parameter(typeof(object), "instance");
 6865        var instanceExpression = Expression.Convert(instanceParameter, member.DeclaringType);
 6866
 6867        var valueParameter = Expression.Parameter(typeof(T), "value");
 6868        var canWrite       = true;
 6869
 6870        UnaryExpression  valueExpression;
 6871        Expression memberExpression;
 6872
 6873        switch (member) {
 6874          case PropertyInfo property:
 6875            valueExpression  = Expression.Convert(valueParameter, property.PropertyType);
 6876            memberExpression = Expression.Property(instanceExpression, property);
 6877            canWrite         = property.CanWrite;
 6878            break;
 6879          case FieldInfo field:
 6880            valueExpression  = Expression.Convert(valueParameter, field.FieldType);
 6881            memberExpression = Expression.Field(instanceExpression, field);
 6882            canWrite         = field.IsInitOnly == false;
 6883            break;
 6884          case MethodInfo method when method.GetParameters().Length == 0:
 6885            valueExpression  = null;
 6886            memberExpression = Expression.Call(instanceExpression, method);
 6887            canWrite         = false;
 6888            break;
 6889          default:
 6890            throw new InvalidOperationException($"Unsupported member type {member.GetType().Name}");
 6891        }
 6892
 6893        var getExpression = Expression.Convert(memberExpression, typeof(T));
 6894        var getLambda     = Expression.Lambda<Func<object, T>>(getExpression, instanceParameter);
 6895        var getter = getLambda.Compile();
 6896
 6897        Action<object, T> setter = null;
 6898        if (canWrite) {
 6899          var setExpression = Expression.Assign(memberExpression, valueExpression);
 6900          var setLambda     = Expression.Lambda<Action<object, T>>(setExpression, instanceParameter, valueParameter);
 6901          setter = setLambda.Compile();
 6902        }
 6903
 6904        return new InstanceAccessor<T> {
 6905          GetValue = getter,
 6906          SetValue = setter
 6907        };
 6908      } catch (Exception ex) {
 6909        throw new InvalidOperationException($"Failed to create accessor for {member.DeclaringType}.{member.Name}", ex);
 6910      }
 6911    }
 6912
 6913    public struct InstanceAccessor<TValue> {
 6914      public Func<object, TValue>   GetValue;
 6915      public Action<object, TValue> SetValue;
 6916    }
 6917
 6918    public struct StaticAccessor<TValue> {
 6919      public Func<TValue>   GetValue;
 6920      public Action<TValue> SetValue;
 6921    }
 6922
 6923    internal static class DelegateSwizzle<In0, In1> {
 6924      public static DelegateSwizzle Make<Out0>(Expression<Func<In0, In1, Out0>> out0) {
 6925        return new DelegateSwizzle(new Expression[] { out0 }, new [] { typeof(Out0)});
 6926      }
 6927
 6928      public static DelegateSwizzle Make<Out0, Out1>(Expression<Func<In0, In1, Out0>> out0, Expression<Func<In0, In1, Ou
 6929        return new DelegateSwizzle(new Expression[] { out0, out1 }, new [] { typeof(Out0), typeof(Out1)});
 6930      }
 6931
 6932      public static DelegateSwizzle Make<Out0, Out1, Out3>(Expression<Func<In0, In1, Out0>> out0, Expression<Func<In0, I
 6933        return new DelegateSwizzle(new Expression[] { out0, out1, out3 }, new [] { typeof(Out0), typeof(Out1), typeof(Ou
 6934      }
 6935    }
 6936
 6937    internal class DelegateSwizzle {
 6938      public DelegateSwizzle(Expression[] converters, Type[] types) {
 6939        Converters = converters;
 6940        Types = types;
 6941      }
 6942
 6943      public Expression[] Converters { get; }
 6944      public Type[] Types { get; }
 6945    }
 6946
 6947#if UNITY_EDITOR
 6948
 6949    public static T CreateEditorMethodDelegate<T>(string editorAssemblyTypeName, string methodName, BindingFlags flags) 
 6950      return CreateMethodDelegate<T>(typeof(Editor).Assembly, editorAssemblyTypeName, methodName, flags);
 6951    }
 6952
 6953    public static Delegate CreateEditorMethodDelegate(string editorAssemblyTypeName, string methodName, BindingFlags fla
 6954      return CreateMethodDelegate(typeof(Editor).Assembly, editorAssemblyTypeName, methodName, flags, delegateType);
 6955    }
 6956
 6957#endif
 6958  }
 6959}
 6960
 6961#endregion
 6962
 6963
 6964#region SerializedPropertyUtilities.cs
 6965
 6966namespace Fusion.Editor {
 6967  using System;
 6968  using System.Collections;
 6969  using System.Collections.Generic;
 6970  using System.Text.RegularExpressions;
 6971  using UnityEditor;
 6972
 6973  static partial class SerializedPropertyUtilities {
 6974    private static readonly Regex _arrayElementRegex = new(@"\.Array\.data\[\d+\]$", RegexOptions.Compiled);
 6975
 6976    public static SerializedProperty FindPropertyOrThrow(this SerializedObject so, string propertyPath) {
 6977      var result = so.FindProperty(propertyPath);
 6978      if (result == null) {
 6979        throw new ArgumentOutOfRangeException(nameof(propertyPath), $"Property not found: {propertyPath} on {so.targetOb
 6980      }
 6981
 6982      return result;
 6983    }
 6984
 6985    public static SerializedProperty FindPropertyRelativeOrThrow(this SerializedProperty sp, string relativePropertyPath
 6986      var result = sp.FindPropertyRelative(relativePropertyPath);
 6987      if (result == null) {
 6988        throw new ArgumentOutOfRangeException(nameof(relativePropertyPath), $"Property not found: {relativePropertyPath}
 6989      }
 6990
 6991      return result;
 6992    }
 6993
 6994    public static SerializedProperty FindPropertyRelativeToParentOrThrow(this SerializedProperty property, string relati
 6995      var result = FindPropertyRelativeToParent(property, relativePath);
 6996      if (result == null) {
 6997        throw new ArgumentOutOfRangeException(nameof(relativePath), $"Property not found: {relativePath} (relative to th
 6998      }
 6999
 7000      return result;
 7001    }
 7002
 7003    public static SerializedProperty FindPropertyRelativeToParent(this SerializedProperty property, string relativePath)
 7004
 7005      var parentPath = property.propertyPath;
 7006      int startIndex = 0;
 7007
 7008      do {
 7009        // array element?
 7010        if (parentPath.EndsWith("]")) {
 7011          var match = _arrayElementRegex.Match(parentPath);
 7012          if (match.Success) {
 7013            parentPath = parentPath.Substring(0, match.Index);
 7014          }
 7015        }
 7016
 7017        var lastDotIndex = parentPath.LastIndexOf('.');
 7018        if (lastDotIndex < 0) {
 7019          if (string.IsNullOrEmpty(parentPath)) {
 7020            return null;
 7021          }
 7022
 7023          parentPath = string.Empty;
 7024        } else {
 7025          parentPath = parentPath.Substring(0, lastDotIndex);
 7026        }
 7027
 7028      } while (relativePath[startIndex++] == '^');
 7029
 7030      if (startIndex > 1) {
 7031        relativePath = relativePath.Substring(startIndex - 1);
 7032      }
 7033
 7034      if (string.IsNullOrEmpty(parentPath)) {
 7035        return property.serializedObject.FindProperty(relativePath);
 7036      } else {
 7037        return property.serializedObject.FindProperty(parentPath + "." + relativePath);
 7038      }
 7039    }
 7040
 7041    public static bool IsArrayElement(this SerializedProperty sp) {
 7042      var propertyPath = sp.propertyPath;
 7043      if (!propertyPath.EndsWith("]", StringComparison.Ordinal)) {
 7044        return false;
 7045      }
 7046
 7047      return true;
 7048    }
 7049
 7050    public static bool IsArrayElement(this SerializedProperty sp, out int index) {
 7051      var propertyPath = sp.propertyPath;
 7052      if (!propertyPath.EndsWith("]", StringComparison.Ordinal)) {
 7053        index = -1;
 7054        return false;
 7055      }
 7056
 7057      var indexStart = propertyPath.LastIndexOf("[", StringComparison.Ordinal);
 7058      if (indexStart < 0) {
 7059        index = -1;
 7060        return false;
 7061      }
 7062
 7063      index = int.Parse(propertyPath.Substring(indexStart + 1, propertyPath.Length - indexStart - 2));
 7064      return true;
 7065    }
 7066
 7067    public static SerializedProperty GetArrayFromArrayElement(this SerializedProperty sp) {
 7068      var path  = sp.propertyPath;
 7069      var match = _arrayElementRegex.Match(path);
 7070      if (!match.Success) {
 7071        throw new ArgumentException($"Property is not an array element: {path}");
 7072      }
 7073
 7074      var arrayPath = path.Substring(0, match.Index);
 7075      return sp.serializedObject.FindProperty(arrayPath);
 7076    }
 7077
 7078    public static bool IsArrayProperty(this SerializedProperty sp) {
 7079      return sp.isArray && sp.propertyType != SerializedPropertyType.String;
 7080    }
 7081
 7082    public static SerializedPropertyEnumerable GetChildren(this SerializedProperty property, bool visibleOnly = true) {
 7083      return new SerializedPropertyEnumerable(property, visibleOnly);
 7084    }
 7085
 7086    public class SerializedPropertyEqualityComparer : IEqualityComparer<SerializedProperty> {
 7087      public static SerializedPropertyEqualityComparer Instance = new();
 7088
 7089      public bool Equals(SerializedProperty x, SerializedProperty y) {
 7090        return SerializedProperty.DataEquals(x, y);
 7091      }
 7092
 7093      public int GetHashCode(SerializedProperty p) {
 7094        bool enterChildren;
 7095        var  isFirst  = true;
 7096        var  hashCode = 0;
 7097        var  minDepth = p.depth + 1;
 7098
 7099        do {
 7100          enterChildren = false;
 7101
 7102          switch (p.propertyType) {
 7103            case SerializedPropertyType.Integer:
 7104              hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.intValue);
 7105              break;
 7106            case SerializedPropertyType.Boolean:
 7107              hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.boolValue.GetHashCode());
 7108              break;
 7109            case SerializedPropertyType.Float:
 7110              hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.floatValue.GetHashCode());
 7111              break;
 7112            case SerializedPropertyType.String:
 7113              hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.stringValue.GetHashCode());
 7114              break;
 7115            case SerializedPropertyType.Color:
 7116              hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.colorValue.GetHashCode());
 7117              break;
 7118            case SerializedPropertyType.ObjectReference:
 7119              hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.objectReferenceInstanceIDValue);
 7120              break;
 7121            case SerializedPropertyType.LayerMask:
 7122              hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.intValue);
 7123              break;
 7124            case SerializedPropertyType.Enum:
 7125              hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.intValue);
 7126              break;
 7127            case SerializedPropertyType.Vector2:
 7128              hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.vector2Value.GetHashCode());
 7129              break;
 7130            case SerializedPropertyType.Vector3:
 7131              hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.vector3Value.GetHashCode());
 7132              break;
 7133            case SerializedPropertyType.Vector4:
 7134              hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.vector4Value.GetHashCode());
 7135              break;
 7136            case SerializedPropertyType.Vector2Int:
 7137              hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.vector2IntValue.GetHashCode());
 7138              break;
 7139            case SerializedPropertyType.Vector3Int:
 7140              hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.vector3IntValue.GetHashCode());
 7141              break;
 7142            case SerializedPropertyType.Rect:
 7143              hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.rectValue.GetHashCode());
 7144              break;
 7145            case SerializedPropertyType.RectInt:
 7146              hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.rectIntValue.GetHashCode());
 7147              break;
 7148            case SerializedPropertyType.ArraySize:
 7149              hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.intValue);
 7150              break;
 7151            case SerializedPropertyType.Character:
 7152              hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.intValue.GetHashCode());
 7153              break;
 7154            case SerializedPropertyType.AnimationCurve:
 7155              hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.animationCurveValue.GetHashCode());
 7156              break;
 7157            case SerializedPropertyType.Bounds:
 7158              hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.boundsValue.GetHashCode());
 7159              break;
 7160            case SerializedPropertyType.BoundsInt:
 7161              hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.boundsIntValue.GetHashCode());
 7162              break;
 7163            case SerializedPropertyType.ExposedReference:
 7164              hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.exposedReferenceValue.GetHashCode());
 7165              break;
 7166            default: {
 7167              enterChildren = true;
 7168              break;
 7169            }
 7170          }
 7171
 7172          if (isFirst) {
 7173            if (!enterChildren)
 7174              // no traverse needed
 7175            {
 7176              return hashCode;
 7177            }
 7178
 7179            // since property is going to be traversed, a copy needs to be made
 7180            p       = p.Copy();
 7181            isFirst = false;
 7182          }
 7183        } while (p.Next(enterChildren) && p.depth >= minDepth);
 7184
 7185        return hashCode;
 7186      }
 7187    }
 7188
 7189    public struct SerializedPropertyEnumerable : IEnumerable<SerializedProperty> {
 7190      private SerializedProperty property;
 7191      private bool               visible;
 7192
 7193      public SerializedPropertyEnumerable(SerializedProperty property, bool visible) {
 7194        this.property = property;
 7195        this.visible  = visible;
 7196      }
 7197
 7198      public SerializedPropertyEnumerator GetEnumerator() {
 7199        return new SerializedPropertyEnumerator(property, visible);
 7200      }
 7201
 7202      IEnumerator<SerializedProperty> IEnumerable<SerializedProperty>.GetEnumerator() {
 7203        return GetEnumerator();
 7204      }
 7205
 7206      IEnumerator IEnumerable.GetEnumerator() {
 7207        return GetEnumerator();
 7208      }
 7209    }
 7210
 7211    public struct SerializedPropertyEnumerator : IEnumerator<SerializedProperty> {
 7212      private SerializedProperty current;
 7213      private bool               enterChildren;
 7214      private bool               visible;
 7215      private int                parentDepth;
 7216
 7217      public SerializedPropertyEnumerator(SerializedProperty parent, bool visible) {
 7218        current       = parent.Copy();
 7219        enterChildren = true;
 7220        parentDepth   = parent.depth;
 7221        this.visible  = visible;
 7222      }
 7223
 7224      public SerializedProperty Current => current;
 7225
 7226      SerializedProperty IEnumerator<SerializedProperty>.Current => current;
 7227
 7228      object IEnumerator.Current => current;
 7229
 7230      public void Dispose() {
 7231        current.Dispose();
 7232      }
 7233
 7234      public bool MoveNext() {
 7235        bool entered = visible ? current.NextVisible(enterChildren) : current.Next(enterChildren);
 7236        enterChildren = false;
 7237        if (!entered) {
 7238          return false;
 7239        }
 7240        if (current.depth <= parentDepth) {
 7241          return false;
 7242        }
 7243        return true;
 7244      }
 7245
 7246      public void Reset() {
 7247        throw new NotImplementedException();
 7248      }
 7249    }
 7250
 7251    private static int[] _updateFixedBufferTemp = Array.Empty<int>();
 7252
 7253    internal static bool UpdateFixedBuffer(this SerializedProperty sp, Action<int[], int> fill, Action<int[], int> updat
 7254      int count = sp.fixedBufferSize;
 7255      Array.Resize(ref _updateFixedBufferTemp, Math.Max(_updateFixedBufferTemp.Length, count));
 7256
 7257      // need to get to the first property... `GetFixedBufferElementAtIndex` is slow and allocates
 7258
 7259      var element = sp.Copy();
 7260      element.Next(true); // .Array
 7261      element.Next(true); // .Array.size
 7262      element.Next(true); // .Array.data[0]
 7263
 7264      unsafe {
 7265        fixed (int* p = _updateFixedBufferTemp) {
 7266          Unity.Collections.LowLevel.Unsafe.UnsafeUtility.MemClear(p, count * sizeof(int));
 7267        }
 7268
 7269        fill(_updateFixedBufferTemp, count);
 7270
 7271        int i = 0;
 7272        if (!force) {
 7273          // find the first difference
 7274          for (; i < count; ++i, element.Next(true)) {
 7275            FusionEditorLog.Assert(element.propertyType == SerializedPropertyType.Integer, "Invalid property type, expec
 7276            if (element.intValue != _updateFixedBufferTemp[i]) {
 7277              break;
 7278            }
 7279          }
 7280        }
 7281
 7282        if (i < count) {
 7283          // update data
 7284          if (write) {
 7285            for (; i < count; ++i, element.Next(true)) {
 7286              element.intValue = _updateFixedBufferTemp[i];
 7287            }
 7288          } else {
 7289            for (; i < count; ++i, element.Next(true)) {
 7290              _updateFixedBufferTemp[i] = element.intValue;
 7291            }
 7292          }
 7293
 7294          update(_updateFixedBufferTemp, count);
 7295          return true;
 7296        } else {
 7297          return false;
 7298        }
 7299      }
 7300    }
 7301  }
 7302}
 7303
 7304#endregion
 7305
 7306
 7307#region UnityInternal.cs
 7308
 7309// ReSharper disable InconsistentNaming
 7310#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
 7311namespace Fusion.Editor {
 7312  using System;
 7313  using System.Collections;
 7314  using System.Collections.Generic;
 7315  using System.Linq;
 7316  using System.Reflection;
 7317  using UnityEditor;
 7318  using UnityEngine;
 7319  using static ReflectionUtils;
 7320
 7321
 7322  static partial class UnityInternal {
 7323
 7324    static Assembly FindAssembly(string name) {
 7325      return AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == name);
 7326    }
 7327
 7328    [UnityEditor.InitializeOnLoad]
 7329    public static class Event {
 7330      static readonly StaticAccessor<UnityEngine.Event> s_Current_ = typeof(UnityEngine.Event).CreateStaticFieldAccessor
 7331      public static UnityEngine.Event s_Current => s_Current_.GetValue();
 7332    }
 7333
 7334    [UnityEditor.InitializeOnLoad]
 7335    public static class Editor {
 7336      public delegate bool DoDrawDefaultInspectorDelegate(SerializedObject obj);
 7337      public delegate void BoolSetterDelegate(UnityEditor.Editor editor, bool value);
 7338
 7339      public static readonly DoDrawDefaultInspectorDelegate DoDrawDefaultInspector = typeof(UnityEditor.Editor).CreateMe
 7340      public static readonly BoolSetterDelegate             InternalSetHidden      = typeof(UnityEditor.Editor).CreateMe
 7341    }
 7342
 7343
 7344    [UnityEditor.InitializeOnLoad]
 7345    public static class EditorGUI {
 7346      public delegate string DelayedTextFieldInternalDelegate(Rect position, int id, GUIContent label, string value, str
 7347      public delegate Rect   MultiFieldPrefixLabelDelegate(Rect totalPosition, int id, GUIContent label, int columns);
 7348      public delegate string TextFieldInternalDelegate(int id, Rect position, string text, GUIStyle style);
 7349      public delegate string ToolbarSearchFieldDelegate(int id, Rect position, string text, bool showWithPopupArrow);
 7350      public delegate bool   DefaultPropertyFieldDelegate(Rect position, UnityEditor.SerializedProperty property, GUICon
 7351
 7352
 7353      public static readonly MultiFieldPrefixLabelDelegate    MultiFieldPrefixLabel    = typeof(UnityEditor.EditorGUI).C
 7354      public static readonly TextFieldInternalDelegate        TextFieldInternal        = typeof(UnityEditor.EditorGUI).C
 7355      public static readonly ToolbarSearchFieldDelegate       ToolbarSearchField       = typeof(UnityEditor.EditorGUI).C
 7356      public static readonly DelayedTextFieldInternalDelegate DelayedTextFieldInternal = typeof(UnityEditor.EditorGUI).C
 7357      public static readonly DefaultPropertyFieldDelegate     DefaultPropertyField     = typeof(UnityEditor.EditorGUI).C
 7358
 7359      private static readonly FieldInfo             s_TextFieldHash           = typeof(UnityEditor.EditorGUI).GetFieldOr
 7360      private static readonly FieldInfo             s_DelayedTextFieldHash    = typeof(UnityEditor.EditorGUI).GetFieldOr
 7361      private static readonly StaticAccessor<float> s_indent                  = typeof(UnityEditor.EditorGUI).CreateStat
 7362      public static readonly  Action                EndEditingActiveTextField = typeof(UnityEditor.EditorGUI).CreateMeth
 7363
 7364      public static   int   TextFieldHash        => (int)s_TextFieldHash.GetValue(null);
 7365      public static   int   DelayedTextFieldHash => (int)s_DelayedTextFieldHash.GetValue(null);
 7366      internal static float indent               => s_indent.GetValue();
 7367    }
 7368
 7369    [UnityEditor.InitializeOnLoad]
 7370    public static class EditorUtility {
 7371      public delegate void DisplayCustomMenuDelegate(Rect position, string[] options, int[] selected, UnityEditor.Editor
 7372
 7373      public static DisplayCustomMenuDelegate DisplayCustomMenu = typeof(UnityEditor.EditorUtility).CreateMethodDelegate
 7374    }
 7375
 7376    [UnityEditor.InitializeOnLoad]
 7377    public static class GUIClip {
 7378      public static Type InternalType = typeof(UnityEngine.GUIUtility).Assembly.GetType("UnityEngine.GUIClip", true);
 7379
 7380      private static readonly StaticAccessor<Rect> _visibleRect = InternalType.CreateStaticPropertyAccessor<Rect>(nameof
 7381      public static Rect visibleRect => _visibleRect.GetValue();
 7382    }
 7383
 7384    [UnityEditor.InitializeOnLoad]
 7385    public static class HandleUtility {
 7386      public static readonly Action ApplyWireMaterial = typeof(UnityEditor.HandleUtility).CreateMethodDelegate<Action>(n
 7387    }
 7388
 7389
 7390    [UnityEditor.InitializeOnLoad]
 7391    public static class LayerMatrixGUI {
 7392      private const string TypeName =
 7393#if UNITY_2023_1_OR_NEWER
 7394        "UnityEditor.LayerCollisionMatrixGUI2D";
 7395#else
 7396        "UnityEditor.LayerMatrixGUI";
 7397#endif
 7398
 7399      private static readonly Type InternalType =
 7400#if UNITY_2023_1_OR_NEWER
 7401        FindAssembly("UnityEditor.Physics2DModule")?.GetType(TypeName, true);
 7402#else
 7403        typeof(UnityEditor.Editor).Assembly.GetType(TypeName, true);
 7404#endif
 7405
 7406      private static readonly Type InternalGetValueFuncType = InternalType?.GetNestedTypeOrThrow(nameof(GetValueFunc), B
 7407      private static readonly Type InternalSetValueFuncType = InternalType?.GetNestedTypeOrThrow(nameof(SetValueFunc), B
 7408
 7409#if UNITY_2023_1_OR_NEWER
 7410      private static readonly Delegate _Draw = InternalType?.CreateMethodDelegate(nameof(Draw), BindingFlags.Public | Bi
 7411        typeof(Action<,,>).MakeGenericType(
 7412          typeof(GUIContent), InternalGetValueFuncType, InternalSetValueFuncType)
 7413      );
 7414#else
 7415      private delegate void Ref2Action<T1, T2, T3, T4>(T1 t1, ref T2 t2, T3 t3, T4 t4);
 7416
 7417      private static readonly Delegate _DoGUI = InternalType?.CreateMethodDelegate("DoGUI", BindingFlags.Public | Bindin
 7418        typeof(Ref2Action<,,,>).MakeGenericType(
 7419          typeof(GUIContent), typeof(bool), InternalGetValueFuncType, InternalSetValueFuncType)
 7420      );
 7421#endif
 7422
 7423      public delegate bool GetValueFunc(int layerA, int layerB);
 7424      public delegate void SetValueFunc(int layerA, int layerB, bool val);
 7425
 7426      public static void Draw(GUIContent label, GetValueFunc getValue, SetValueFunc setValue) {
 7427        if (InternalType == null) {
 7428          throw new InvalidOperationException($"{TypeName} not found");
 7429        }
 7430
 7431        var getter = Delegate.CreateDelegate(InternalGetValueFuncType, getValue.Target, getValue.Method);
 7432        var setter = Delegate.CreateDelegate(InternalSetValueFuncType, setValue.Target, setValue.Method);
 7433
 7434#if UNITY_2023_1_OR_NEWER
 7435        _Draw.DynamicInvoke(label, getter, setter);
 7436#else
 7437        bool show = true;
 7438        var args = new object[] { label, show, getter, setter };
 7439        _DoGUI.DynamicInvoke(args);
 7440#endif
 7441      }
 7442    }
 7443
 7444
 7445    [UnityEditor.InitializeOnLoad]
 7446    public static class DecoratorDrawer {
 7447      private static InstanceAccessor<PropertyAttribute> m_Attribute = typeof(UnityEditor.DecoratorDrawer).CreateFieldAc
 7448
 7449      public static void SetAttribute(UnityEditor.DecoratorDrawer drawer, PropertyAttribute attribute) {
 7450        m_Attribute.SetValue(drawer, attribute);
 7451      }
 7452    }
 7453
 7454    [UnityEditor.InitializeOnLoad]
 7455    public static class PropertyDrawer {
 7456      private static InstanceAccessor<PropertyAttribute> m_Attribute = typeof(UnityEditor.PropertyDrawer).CreateFieldAcc
 7457      private static InstanceAccessor<FieldInfo>         m_FieldInfo = typeof(UnityEditor.PropertyDrawer).CreateFieldAcc
 7458
 7459      public static void SetAttribute(UnityEditor.PropertyDrawer drawer, PropertyAttribute attribute) {
 7460        m_Attribute.SetValue(drawer, attribute);
 7461      }
 7462
 7463      public static void SetFieldInfo(UnityEditor.PropertyDrawer drawer, FieldInfo fieldInfo) {
 7464        m_FieldInfo.SetValue(drawer, fieldInfo);
 7465      }
 7466    }
 7467
 7468    [UnityEditor.InitializeOnLoad]
 7469    public static class EditorGUIUtility {
 7470      private static readonly StaticAccessor<int> s_LastControlID = typeof(UnityEditor.EditorGUIUtility).CreateStaticFie
 7471
 7472      private static readonly StaticAccessor<float> _contentWidth = typeof(UnityEditor.EditorGUIUtility).CreateStaticPro
 7473      public static           int                   LastControlID => s_LastControlID.GetValue();
 7474      public static           float                 contextWidth  => _contentWidth.GetValue();
 7475
 7476      public delegate UnityEngine.Object GetScriptDelegate(string scriptClass);
 7477      public delegate Texture2D          GetIconForObjectDelegate(UnityEngine.Object obj);
 7478      public delegate GUIContent         TempContentDelegate(string text);
 7479      public delegate Texture2D          GetHelpIconDelegate(MessageType type);
 7480
 7481      public static readonly GetScriptDelegate        GetScript        = typeof(UnityEditor.EditorGUIUtility).CreateMeth
 7482      public static readonly GetIconForObjectDelegate GetIconForObject = typeof(UnityEditor.EditorGUIUtility).CreateMeth
 7483      public static readonly TempContentDelegate      TempContent      = typeof(UnityEditor.EditorGUIUtility).CreateMeth
 7484      public static readonly GetHelpIconDelegate      GetHelpIcon      = typeof(UnityEditor.EditorGUIUtility).CreateMeth
 7485    }
 7486
 7487    [UnityEditor.InitializeOnLoad]
 7488    public static class HierarchyProperty {
 7489      public delegate void CopySearchFilterFromDelegate(UnityEditor.HierarchyProperty to, UnityEditor.HierarchyProperty 
 7490      public static CopySearchFilterFromDelegate CopySearchFilterFrom = typeof(UnityEditor.HierarchyProperty).CreateMeth
 7491        BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
 7492    }
 7493
 7494    [UnityEditor.InitializeOnLoad]
 7495    public static class ScriptAttributeUtility {
 7496
 7497      public static readonly Type InternalType = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.ScriptAttribut
 7498
 7499      public delegate FieldInfo GetFieldInfoFromPropertyDelegate(SerializedProperty property, out Type type);
 7500      public static readonly GetFieldInfoFromPropertyDelegate GetFieldInfoFromProperty =
 7501        InternalType.CreateMethodDelegate<GetFieldInfoFromPropertyDelegate>(
 7502          "GetFieldInfoFromProperty",
 7503          BindingFlags.Static | BindingFlags.NonPublic);
 7504
 7505      public delegate Type GetDrawerTypeForTypeDelegate(Type type, bool isManagedReference);
 7506      public static readonly GetDrawerTypeForTypeDelegate GetDrawerTypeForType =
 7507        InternalType.CreateMethodDelegate<GetDrawerTypeForTypeDelegate>(
 7508          "GetDrawerTypeForType",
 7509          BindingFlags.Static | BindingFlags.NonPublic,
 7510          null,
 7511          DelegateSwizzle<Type, bool>.Make((t, b) => t), // post 2023.3
 7512          DelegateSwizzle<Type, bool>.Make((t, b) => t, (t, b) => (Type[])null, (t, b) => b) // pre 2023.3.23
 7513        );
 7514
 7515      public delegate Type GetDrawerTypeForPropertyAndTypeDelegate(SerializedProperty property, Type type);
 7516      public static readonly GetDrawerTypeForPropertyAndTypeDelegate GetDrawerTypeForPropertyAndType =
 7517        InternalType.CreateMethodDelegate<GetDrawerTypeForPropertyAndTypeDelegate>(
 7518          "GetDrawerTypeForPropertyAndType",
 7519          BindingFlags.Static | BindingFlags.NonPublic);
 7520
 7521      private static readonly GetHandlerDelegate _GetHandler = InternalType.CreateMethodDelegate<GetHandlerDelegate>("Ge
 7522        MakeFuncType(typeof(SerializedProperty), PropertyHandler.InternalType)
 7523      );
 7524
 7525      public delegate List<PropertyAttribute> GetFieldAttributesDelegate(FieldInfo field);
 7526      public static readonly GetFieldAttributesDelegate GetFieldAttributes = InternalType.CreateMethodDelegate<GetFieldA
 7527
 7528      private static readonly StaticAccessor<object> _propertyHandlerCache = InternalType.CreateStaticPropertyAccessor(n
 7529
 7530      private static readonly StaticAccessor<object> s_SharedNullHandler = InternalType.CreateStaticFieldAccessor("s_Sha
 7531      private static readonly StaticAccessor<object> s_NextHandler       = InternalType.CreateStaticFieldAccessor("s_Nex
 7532
 7533      public static PropertyHandlerCache propertyHandlerCache => new() {
 7534        _instance = _propertyHandlerCache.GetValue()
 7535      };
 7536
 7537      public static PropertyHandler sharedNullHandler => PropertyHandler.Wrap(s_SharedNullHandler.GetValue());
 7538      public static PropertyHandler nextHandler => PropertyHandler.Wrap(s_NextHandler.GetValue());
 7539
 7540      public static PropertyHandler GetHandler(SerializedProperty property) {
 7541        return PropertyHandler.Wrap(_GetHandler(property));
 7542      }
 7543
 7544      private delegate object GetHandlerDelegate(SerializedProperty property);
 7545    }
 7546
 7547    public struct PropertyHandlerCache {
 7548      [UnityEditor.InitializeOnLoad]
 7549      private static class Statics {
 7550        public static readonly Type                    InternalType    = typeof(UnityEditor.Editor).Assembly.GetType("Un
 7551        public static readonly GetPropertyHashDelegate GetPropertyHash = InternalType.CreateMethodDelegate<GetPropertyHa
 7552
 7553        public static readonly GetHandlerDelegate GetHandler = InternalType.CreateMethodDelegate<GetHandlerDelegate>(nam
 7554          MakeFuncType(InternalType, typeof(SerializedProperty), PropertyHandler.InternalType));
 7555
 7556        public static readonly SetHandlerDelegate SetHandler = InternalType.CreateMethodDelegate<SetHandlerDelegate>(nam
 7557          MakeActionType(InternalType, typeof(SerializedProperty), PropertyHandler.InternalType));
 7558
 7559        public static readonly FieldInfo m_PropertyHandlers = InternalType.GetFieldOrThrow(nameof(m_PropertyHandlers));
 7560      }
 7561
 7562      public static Type InternalType => Statics.InternalType;
 7563
 7564      public delegate int GetPropertyHashDelegate(SerializedProperty property);
 7565
 7566      public delegate object GetHandlerDelegate(object instance, SerializedProperty property);
 7567
 7568      public delegate void SetHandlerDelegate(object instance, SerializedProperty property, object handlerInstance);
 7569
 7570      public object _instance;
 7571
 7572      public PropertyHandler GetHandler(SerializedProperty property) {
 7573        return new PropertyHandler {
 7574          _instance = Statics.GetHandler(_instance, property)
 7575        };
 7576      }
 7577
 7578      public void SetHandler(SerializedProperty property, PropertyHandler newHandler) {
 7579        Statics.SetHandler(_instance, property, newHandler._instance);
 7580      }
 7581
 7582      public IEnumerable<(int, PropertyHandler)> PropertyHandlers {
 7583        get {
 7584          var dict = (IDictionary)Statics.m_PropertyHandlers.GetValue(_instance);
 7585          foreach (DictionaryEntry entry in dict) {
 7586            yield return ((int)entry.Key, PropertyHandler.Wrap(entry.Value));
 7587          }
 7588        }
 7589      }
 7590    }
 7591
 7592    public struct PropertyHandler : IEquatable<PropertyHandler> {
 7593      [UnityEditor.InitializeOnLoad]
 7594      private static class Statics {
 7595        public static readonly Type                                                InternalType       = typeof(UnityEdit
 7596        public static readonly InstanceAccessor<List<UnityEditor.DecoratorDrawer>> m_DecoratorDrawers = InternalType.Cre
 7597        public static readonly InstanceAccessor<List<UnityEditor.PropertyDrawer>> m_PropertyDrawers = InternalType.Creat
 7598      }
 7599
 7600
 7601      public static Type InternalType => Statics.InternalType;
 7602
 7603      public object _instance;
 7604
 7605      internal static PropertyHandler Wrap(object instance) {
 7606        return new() {
 7607          _instance = instance
 7608        };
 7609      }
 7610
 7611      public static PropertyHandler New() {
 7612        return Wrap(Activator.CreateInstance(InternalType));
 7613      }
 7614
 7615      public List<UnityEditor.PropertyDrawer> m_PropertyDrawers {
 7616        get => Statics.m_PropertyDrawers.GetValue(_instance);
 7617        set => Statics.m_PropertyDrawers.SetValue(_instance, value);
 7618      }
 7619
 7620      public bool Equals(PropertyHandler other) {
 7621        return _instance == other._instance;
 7622      }
 7623
 7624      public override int GetHashCode() {
 7625        return _instance?.GetHashCode() ?? 0;
 7626      }
 7627
 7628      public override bool Equals(object obj) {
 7629        return obj is PropertyHandler h ? Equals(h) : false;
 7630      }
 7631
 7632      public List<UnityEditor.DecoratorDrawer> decoratorDrawers {
 7633        get => Statics.m_DecoratorDrawers.GetValue(_instance);
 7634        set => Statics.m_DecoratorDrawers.SetValue(_instance, value);
 7635      }
 7636    }
 7637
 7638    [UnityEditor.InitializeOnLoad]
 7639    public static class EditorApplication {
 7640      public static readonly Action Internal_CallAssetLabelsHaveChanged = typeof(UnityEditor.EditorApplication).CreateMe
 7641    }
 7642
 7643    public struct ObjectSelector {
 7644      [UnityEditor.InitializeOnLoad]
 7645      private static class Statics {
 7646        public static readonly Type                         InternalType  = typeof(UnityEditor.Editor).Assembly.GetType(
 7647        public static readonly StaticAccessor<bool>         _tooltip      = InternalType.CreateStaticPropertyAccessor<bo
 7648        public static readonly StaticAccessor<EditorWindow> _get          = InternalType.CreateStaticPropertyAccessor<Ed
 7649        public static readonly InstanceAccessor<string>     _searchFilter = InternalType.CreatePropertyAccessor<string>(
 7650      }
 7651
 7652      private EditorWindow _instance;
 7653
 7654      public static bool isVisible => Statics._tooltip.GetValue();
 7655
 7656      public static ObjectSelector get => new() {
 7657        _instance = Statics._get.GetValue()
 7658      };
 7659
 7660      public string searchFilter {
 7661        get => Statics._searchFilter.GetValue(_instance);
 7662        set => Statics._searchFilter.SetValue(_instance, value);
 7663      }
 7664
 7665      private static readonly InstanceAccessor<int> _objectSelectorID = Statics.InternalType.CreateFieldAccessor<int>(na
 7666      public                  int                   objectSelectorID => _objectSelectorID.GetValue(_instance);
 7667    }
 7668
 7669    [UnityEditor.InitializeOnLoad]
 7670    public class InspectorWindow {
 7671      public static readonly Type                   InternalType      = typeof(UnityEditor.Editor).Assembly.GetType("Uni
 7672      public static readonly InstanceAccessor<bool> _isLockedAccessor = InternalType.CreatePropertyAccessor<bool>(nameof
 7673
 7674      private readonly EditorWindow _instance;
 7675
 7676      public InspectorWindow(EditorWindow instance) {
 7677        if (instance == null) {
 7678          throw new ArgumentNullException(nameof(instance));
 7679        }
 7680
 7681        _instance = instance;
 7682      }
 7683
 7684      public bool isLocked {
 7685        get => _isLockedAccessor.GetValue(_instance);
 7686        set => _isLockedAccessor.SetValue(_instance, value);
 7687      }
 7688    }
 7689
 7690    [UnityEditor.InitializeOnLoad]
 7691    public static class SplitterGUILayout {
 7692      public static readonly Action EndHorizontalSplit = CreateMethodDelegate<Action>(typeof(UnityEditor.Editor).Assembl
 7693        "UnityEditor.SplitterGUILayout", "EndHorizontalSplit", BindingFlags.Public | BindingFlags.Static
 7694      );
 7695
 7696      public static readonly Action EndVerticalSplit = CreateMethodDelegate<Action>(typeof(UnityEditor.Editor).Assembly,
 7697        "UnityEditor.SplitterGUILayout", "EndVerticalSplit", BindingFlags.Public | BindingFlags.Static
 7698      );
 7699
 7700      public static void BeginHorizontalSplit(SplitterState splitterState, GUIStyle style, params GUILayoutOption[] opti
 7701        _beginHorizontalSplit.DynamicInvoke(splitterState.InternalState, style, options);
 7702      }
 7703
 7704      public static void BeginVerticalSplit(SplitterState splitterState, GUIStyle style, params GUILayoutOption[] option
 7705        _beginVerticalSplit.DynamicInvoke(splitterState.InternalState, style, options);
 7706      }
 7707
 7708      private static readonly Delegate _beginHorizontalSplit = CreateMethodDelegate(typeof(UnityEditor.Editor).Assembly,
 7709        "UnityEditor.SplitterGUILayout", "BeginHorizontalSplit", BindingFlags.Public | BindingFlags.Static,
 7710        typeof(Action<,,>).MakeGenericType(SplitterState.InternalType, typeof(GUIStyle), typeof(GUILayoutOption[]))
 7711      );
 7712
 7713      private static readonly Delegate _beginVerticalSplit = CreateMethodDelegate(typeof(UnityEditor.Editor).Assembly,
 7714        "UnityEditor.SplitterGUILayout", "BeginVerticalSplit", BindingFlags.Public | BindingFlags.Static,
 7715        typeof(Action<,,>).MakeGenericType(SplitterState.InternalType, typeof(GUIStyle), typeof(GUILayoutOption[]))
 7716      );
 7717    }
 7718
 7719    [UnityEditor.InitializeOnLoad]
 7720    [Serializable]
 7721    public class SplitterState : ISerializationCallbackReceiver {
 7722
 7723      public static readonly Type InternalType = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.SplitterState"
 7724      private static readonly FieldInfo _relativeSizes = InternalType.GetFieldOrThrow("relativeSizes");
 7725      private static readonly FieldInfo _realSizes = InternalType.GetFieldOrThrow("realSizes");
 7726      private static readonly FieldInfo _splitSize = InternalType.GetFieldOrThrow("splitSize");
 7727
 7728      public string Json = "{}";
 7729
 7730      [NonSerialized]
 7731      public object InternalState = FromRelativeInner(new[] { 1.0f });
 7732
 7733      void ISerializationCallbackReceiver.OnAfterDeserialize() {
 7734        InternalState = JsonUtility.FromJson(Json, InternalType);
 7735      }
 7736
 7737      void ISerializationCallbackReceiver.OnBeforeSerialize() {
 7738        Json = JsonUtility.ToJson(InternalState);
 7739      }
 7740
 7741      public static SplitterState FromRelative(float[] relativeSizes, int[] minSizes = null, int[] maxSizes = null, int 
 7742        var result = new SplitterState();
 7743        result.InternalState = FromRelativeInner(relativeSizes, minSizes, maxSizes, splitSize);
 7744        return result;
 7745      }
 7746
 7747
 7748      private static object FromRelativeInner(float[] relativeSizes, int[] minSizes = null, int[] maxSizes = null, int s
 7749        return Activator.CreateInstance(InternalType, BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPubl
 7750          null,
 7751          new object[] { relativeSizes, minSizes, maxSizes, splitSize },
 7752          null, null);
 7753      }
 7754
 7755      public float[] realSizes => ConvertArray((Array)_realSizes.GetValue(InternalState));
 7756      public float[] relativeSizes => ConvertArray((Array)_relativeSizes.GetValue(InternalState));
 7757      public float splitSize => Convert.ToSingle(_splitSize.GetValue(InternalState));
 7758
 7759      private static float[] ConvertArray(Array value) {
 7760        float[] result = new float[value.Length];
 7761        for (int i = 0; i < value.Length; ++i) {
 7762          result[i] = Convert.ToSingle(value.GetValue(i));
 7763        }
 7764        return result;
 7765      }
 7766    }
 7767
 7768    public sealed class InternalStyles {
 7769      public static InternalStyles Instance = new InternalStyles();
 7770
 7771      internal LazyGUIStyle InspectorTitlebar                => LazyGUIStyle.Create(_ => GetStyle("IN Title"));
 7772      internal LazyGUIStyle FoldoutTitlebar                  => LazyGUIStyle.Create(_ => GetStyle("Titlebar Foldout", "F
 7773      internal LazyGUIStyle BoxWithBorders                   => LazyGUIStyle.Create(_ => GetStyle("OL Box"));
 7774      internal LazyGUIStyle HierarchyTreeViewLine            => LazyGUIStyle.Create(_ => GetStyle("TV Line"));
 7775      internal LazyGUIStyle HierarchyTreeViewSceneBackground => LazyGUIStyle.Create(_ => GetStyle("SceneTopBarBg", "Proj
 7776      internal LazyGUIStyle OptionsButtonStyle               => LazyGUIStyle.Create(_ => GetStyle("PaneOptions"));
 7777      internal LazyGUIStyle AddComponentButton               => LazyGUIStyle.Create(_ => GetStyle("AC Button"));
 7778      internal LazyGUIStyle AnimationEventTooltip            => LazyGUIStyle.Create(_ => GetStyle("AnimationEventTooltip
 7779      internal LazyGUIStyle AnimationEventTooltipArrow       => LazyGUIStyle.Create(_ => GetStyle("AnimationEventTooltip
 7780
 7781      private static GUIStyle GetStyle(params string[] names) {
 7782        var skin = GUI.skin;
 7783
 7784        foreach (var name in names) {
 7785          var result = skin.FindStyle(name);
 7786          if (result != null) {
 7787            return result;
 7788          }
 7789        }
 7790
 7791        throw new ArgumentOutOfRangeException($"Style not found: {string.Join(", ", names)}", nameof(names));
 7792      }
 7793    }
 7794
 7795    public static InternalStyles Styles => InternalStyles.Instance;
 7796  }
 7797}
 7798#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
 7799// ReSharper enable InconsistentNaming
 7800
 7801#endregion
 7802
 7803
 7804#region ArrayLengthAttributeDrawer.Odin.cs
 7805
 7806#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
 7807namespace Fusion.Editor {
 7808  using System;
 7809  using System.Collections;
 7810  using Sirenix.OdinInspector.Editor;
 7811  using UnityEditor;
 7812  using UnityEngine;
 7813
 7814  partial class ArrayLengthAttributeDrawer {
 7815    [FusionOdinAttributeConverter]
 7816    static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, ArrayLengthAttribute attr
 7817      return new[] { new OdinAttributeProxy() { SourceAttribute = attribute } };
 7818    }
 7819
 7820    class OdinAttributeProxy : Attribute {
 7821      public ArrayLengthAttribute SourceAttribute;
 7822    }
 7823
 7824    class OdinDrawer : OdinAttributeDrawer<OdinAttributeProxy> {
 7825      protected override bool CanDrawAttributeProperty(InspectorProperty property) {
 7826        return property.GetUnityPropertyType() == SerializedPropertyType.ArraySize;
 7827      }
 7828
 7829      protected override void DrawPropertyLayout(GUIContent label) {
 7830        var valEntry = Property.ValueEntry;
 7831
 7832        var weakValues = valEntry.WeakValues;
 7833        for (int i = 0; i < weakValues.Count; ++i) {
 7834          var values = (IList)weakValues[i];
 7835          if (values == null) {
 7836            continue;
 7837          }
 7838
 7839          var arraySize = values.Count;
 7840          var attr      = Attribute.SourceAttribute;
 7841          if (arraySize < attr.MinLength) {
 7842            arraySize = attr.MinLength;
 7843          } else if (arraySize > attr.MaxLength) {
 7844            arraySize = attr.MaxLength;
 7845          }
 7846
 7847          if (values.Count != arraySize) {
 7848            if (values is Array array) {
 7849              var newArr = Array.CreateInstance(array.GetType().GetElementType(), arraySize);
 7850              Array.Copy(array, newArr, Math.Min(array.Length, arraySize));
 7851              weakValues.ForceSetValue(i, newArr);
 7852            } else {
 7853              while (values.Count > arraySize) {
 7854                values.RemoveAt(values.Count - 1);
 7855              }
 7856
 7857              while (values.Count < arraySize) {
 7858                values.Add(null);
 7859              }
 7860            }
 7861
 7862            weakValues.ForceMarkDirty();
 7863          }
 7864        }
 7865
 7866        CallNextDrawer(label);
 7867      }
 7868    }
 7869  }
 7870}
 7871#endif
 7872
 7873#endregion
 7874
 7875
 7876#region BinaryDataAttributeDrawer.Odin.cs
 7877
 7878#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
 7879namespace Fusion.Editor {
 7880  using Sirenix.OdinInspector;
 7881
 7882  partial class BinaryDataAttributeDrawer {
 7883    [FusionOdinAttributeConverter]
 7884    static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, BinaryDataAttribute attri
 7885      return new[] { new DrawWithUnityAttribute() };
 7886    }
 7887  }
 7888}
 7889#endif
 7890
 7891#endregion
 7892
 7893
 7894#region DoIfAttributeDrawer.Odin.cs
 7895
 7896#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
 7897namespace Fusion.Editor {
 7898  using System;
 7899  using System.Reflection;
 7900  using Sirenix.OdinInspector.Editor;
 7901  using UnityEngine;
 7902
 7903  partial class DoIfAttributeDrawer {
 7904
 7905    protected abstract class OdinProxyAttributeBase : Attribute {
 7906      public DoIfAttributeBase SourceAttribute;
 7907    }
 7908
 7909    protected abstract class OdinDrawerBase<T> : OdinAttributeDrawer<T> where T : OdinProxyAttributeBase {
 7910      protected override bool CanDrawAttributeProperty(InspectorProperty property) {
 7911        if (property.IsArrayElement(out _)) {
 7912          return false;
 7913        }
 7914
 7915        return true;
 7916      }
 7917
 7918      protected override void DrawPropertyLayout(GUIContent label) {
 7919
 7920        var doIf = this.Attribute.SourceAttribute;
 7921
 7922        bool allPassed = true;
 7923        bool anyPassed = false;
 7924
 7925        var targetProp = Property.FindPropertyRelativeToParent(doIf.ConditionMember);
 7926        if (targetProp == null) {
 7927          var objType = Property.ParentType;
 7928          if (!_cachedGetters.TryGetValue((objType, doIf.ConditionMember), out var getter)) {
 7929            // maybe this is a top-level property then and we can use reflection?
 7930            if (Property.GetValueDepth() != 0) {
 7931              if (doIf.ErrorOnConditionMemberNotFound) {
 7932                FusionEditorLog.ErrorInspector($"Can't check condition for {Property.Path}: non-SerializedProperty check
 7933              }
 7934            } else {
 7935              try {
 7936                _cachedGetters.Add((objType, doIf.ConditionMember), Property.ParentType.CreateGetter(doIf.ConditionMembe
 7937              } catch (Exception e) {
 7938                if (doIf.ErrorOnConditionMemberNotFound) {
 7939                  FusionEditorLog.ErrorInspector($"Can't check condition for {Property.Path}: unable to create getter fo
 7940                }
 7941              }
 7942            }
 7943          }
 7944
 7945          if (getter != null) {
 7946            foreach (var obj in Property.GetValueParent().ValueEntry.WeakValues) {
 7947              var value = getter(obj);
 7948              if (DoIfAttributeDrawer.CheckCondition(doIf, value)) {
 7949                anyPassed = true;
 7950              } else {
 7951                allPassed = false;
 7952              }
 7953            }
 7954          }
 7955        } else {
 7956          foreach (var value in targetProp.ValueEntry.WeakValues) {
 7957            if (DoIfAttributeDrawer.CheckCondition(doIf, value)) {
 7958              anyPassed = true;
 7959            } else {
 7960              allPassed = false;
 7961            }
 7962          }
 7963        }
 7964
 7965        DrawPropertyLayout(label, allPassed, anyPassed);
 7966      }
 7967
 7968      protected abstract void DrawPropertyLayout(GUIContent label, bool allPassed, bool anyPassed);
 7969    }
 7970  }
 7971}
 7972#endif
 7973
 7974#endregion
 7975
 7976
 7977#region DrawIfAttributeDrawer.Odin.cs
 7978
 7979#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
 7980namespace Fusion.Editor {
 7981  using UnityEditor;
 7982  using UnityEngine;
 7983
 7984  partial class DrawIfAttributeDrawer {
 7985
 7986    [FusionOdinAttributeConverter]
 7987    static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, DrawIfAttribute attribute
 7988      return new[] { new OdinAttributeProxy() { SourceAttribute = attribute } };
 7989    }
 7990
 7991    class OdinAttributeProxy : OdinProxyAttributeBase {
 7992    }
 7993
 7994    class OdinDrawer : OdinDrawerBase<OdinAttributeProxy> {
 7995      protected override void DrawPropertyLayout(GUIContent label, bool allPassed, bool anyPassed) {
 7996        var attribute = (DrawIfAttribute)Attribute.SourceAttribute;
 7997        if (!allPassed) {
 7998          if (attribute.Hide) {
 7999            return;
 8000          }
 8001        }
 8002
 8003        using (new EditorGUI.DisabledGroupScope(!allPassed)) {
 8004          base.CallNextDrawer(label);
 8005        }
 8006      }
 8007    }
 8008  }
 8009}
 8010#endif
 8011
 8012#endregion
 8013
 8014
 8015#region DrawInlineAttributeDrawer.Odin.cs
 8016
 8017namespace Fusion.Editor {
 8018  partial class DrawInlineAttributeDrawer {
 8019#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
 8020    [FusionOdinAttributeConverter]
 8021    static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, DrawInlineAttribute attri
 8022      return new System.Attribute[] { new Sirenix.OdinInspector.InlinePropertyAttribute(), new Sirenix.OdinInspector.Hid
 8023    }
 8024#endif
 8025  }
 8026}
 8027
 8028#endregion
 8029
 8030
 8031#region ErrorIfAttributeDrawer.Odin.cs
 8032
 8033#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
 8034namespace Fusion.Editor {
 8035  using UnityEngine;
 8036
 8037  partial class ErrorIfAttributeDrawer {
 8038
 8039    [FusionOdinAttributeConverter]
 8040    static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, ErrorIfAttribute attribut
 8041      return new[] { new OdinAttributeProxy() { SourceAttribute = attribute } };
 8042    }
 8043
 8044    class OdinAttributeProxy : OdinProxyAttributeBase {
 8045    }
 8046
 8047    class OdinDrawer : OdinDrawerBase<OdinAttributeProxy> {
 8048      protected override void DrawPropertyLayout(GUIContent label, bool allPassed, bool anyPassed) {
 8049        var attribute = (ErrorIfAttribute)Attribute.SourceAttribute;
 8050
 8051        base.CallNextDrawer(label);
 8052
 8053        if (anyPassed) {
 8054          using (new FusionEditorGUI.ErrorScope(attribute.Message)) {
 8055          }
 8056        }
 8057      }
 8058    }
 8059  }
 8060}
 8061#endif
 8062
 8063#endregion
 8064
 8065
 8066#region FieldEditorButtonAttributeDrawer.Odin.cs
 8067
 8068#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
 8069namespace Fusion.Editor {
 8070  using System;
 8071  using System.Linq;
 8072  using Sirenix.OdinInspector.Editor;
 8073  using UnityEditor;
 8074  using UnityEngine;
 8075
 8076  partial class FieldEditorButtonAttributeDrawer {
 8077
 8078    [FusionOdinAttributeConverter]
 8079    static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, FieldEditorButtonAttribut
 8080      return new[] { new OdinAttributeProxy() { SourceAttribute = attribute } };
 8081    }
 8082
 8083    class OdinAttributeProxy : Attribute {
 8084      public FieldEditorButtonAttribute SourceAttribute;
 8085    }
 8086
 8087    class OdinDrawer : OdinAttributeDrawer<OdinAttributeProxy> {
 8088      protected override bool CanDrawAttributeProperty(InspectorProperty property) {
 8089        return !property.IsArrayElement(out _);
 8090      }
 8091
 8092      protected override void DrawPropertyLayout(GUIContent label) {
 8093        CallNextDrawer(label);
 8094
 8095        var buttonRect = EditorGUI.IndentedRect(EditorGUILayout.GetControlRect());
 8096        var attribute  = Attribute.SourceAttribute;
 8097        var root       = this.Property.SerializationRoot;
 8098        var targetType = root.ValueEntry.TypeOfValue;
 8099        var targetObjects = root.ValueEntry.WeakValues
 8100         .OfType<UnityEngine.Object>()
 8101         .ToArray();
 8102
 8103        if (DrawButton(buttonRect, attribute, targetType, targetObjects)) {
 8104          this.Property.MarkSerializationRootDirty();
 8105        }
 8106      }
 8107    }
 8108  }
 8109}
 8110#endif
 8111
 8112#endregion
 8113
 8114
 8115#region HideArrayElementLabelAttributeDrawer.Odin.cs
 8116
 8117namespace Fusion.Editor {
 8118  partial class HideArrayElementLabelAttributeDrawer {
 8119#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
 8120    [FusionOdinAttributeConverter]
 8121    static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, HideArrayElementLabelAttr
 8122      // not yet supported
 8123      return System.Array.Empty<System.Attribute>();
 8124    }
 8125#endif
 8126  }
 8127}
 8128
 8129#endregion
 8130
 8131
 8132#region InlineHelpAttributeDrawer.Odin.cs
 8133
 8134#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
 8135namespace Fusion.Editor {
 8136  using System;
 8137  using System.Reflection;
 8138  using Sirenix.OdinInspector.Editor;
 8139  using UnityEditor;
 8140  using UnityEngine;
 8141
 8142  partial class InlineHelpAttributeDrawer {
 8143
 8144    [FusionOdinAttributeConverter]
 8145    static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, InlineHelpAttribute attri
 8146      return new[] { new OdinAttributeProxy() { SourceAttribute = attribute } };
 8147    }
 8148
 8149    class OdinAttributeProxy : Attribute {
 8150      public InlineHelpAttribute SourceAttribute;
 8151    }
 8152
 8153    class OdinDrawer : OdinAttributeDrawer<OdinAttributeProxy> {
 8154      protected override bool CanDrawAttributeProperty(InspectorProperty property) {
 8155        if (property.IsArrayElement(out _)) {
 8156          return false;
 8157        }
 8158
 8159        var helpContent = GetHelpContent(property, true);
 8160        if (helpContent == GUIContent.none) {
 8161          return false;
 8162        }
 8163
 8164        return true;
 8165      }
 8166
 8167      private Rect _lastRect;
 8168
 8169      private bool GetHasFoldout() {
 8170
 8171        var (meta, _) = Property.GetNextPropertyDrawerMetaAttribute(Attribute);
 8172        if (meta != null) {
 8173          return meta.HasFoldout;
 8174        }
 8175
 8176        return Property.GetUnityPropertyType() == SerializedPropertyType.Generic;
 8177      }
 8178
 8179      protected override void DrawPropertyLayout(GUIContent label) {
 8180
 8181        Rect buttonRect  = default;
 8182        bool wasExpanded = false;
 8183
 8184        bool hasFoldout   = GetHasFoldout();
 8185        Rect propertyRect = _lastRect;
 8186        var  helpContent  = GetHelpContent(Property, Attribute.SourceAttribute.ShowTypeHelp);
 8187
 8188        using (new FusionEditorGUI.GUIContentScope(label)) {
 8189
 8190          (wasExpanded, buttonRect) = InlineHelpAttributeDrawer.DrawInlineHelpBeforeProperty(label, helpContent, _lastRe
 8191
 8192          EditorGUILayout.BeginVertical();
 8193          this.CallNextDrawer(label);
 8194          EditorGUILayout.EndVertical();
 8195        }
 8196
 8197        if (Event.current.type == EventType.Repaint) {
 8198          _lastRect = GUILayoutUtility.GetLastRect();
 8199        }
 8200
 8201        if (propertyRect.width > 1 && propertyRect.height > 1) {
 8202
 8203          if (wasExpanded) {
 8204            var height = FusionEditorGUI.GetInlineBoxSize(helpContent).y;
 8205            EditorGUILayout.GetControlRect(false, height);
 8206            propertyRect.height += FusionEditorGUI.GetInlineBoxSize(helpContent).y;
 8207          }
 8208
 8209          DrawInlineHelpAfterProperty(buttonRect, wasExpanded, helpContent, propertyRect);
 8210        }
 8211      }
 8212
 8213      private GUIContent GetHelpContent(InspectorProperty property, bool includeTypeHelp) {
 8214        var parentType = property.ValueEntry.ParentType;
 8215        var memberInfo = parentType.GetFieldIncludingBaseTypes(property.Name, BindingFlags.Public | BindingFlags.NonPubl
 8216        return FusionCodeDoc.FindEntry(memberInfo, includeTypeHelp) ?? GUIContent.none;
 8217      }
 8218
 8219    }
 8220  }
 8221}
 8222#endif
 8223
 8224#endregion
 8225
 8226
 8227#region LayerMatrixAttributeDrawer.Odin.cs
 8228
 8229#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
 8230namespace Fusion.Editor {
 8231  using System;
 8232  using Sirenix.OdinInspector.Editor;
 8233  using UnityEditor;
 8234  using UnityEngine;
 8235
 8236  partial class LayerMatrixAttributeDrawer {
 8237
 8238    [FusionOdinAttributeConverter]
 8239    static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, LayerMatrixAttribute attr
 8240      return new[] { new OdinAttributeProxy() { SourceAttribute = attribute } };
 8241    }
 8242
 8243    class OdinAttributeProxy : Attribute {
 8244      public LayerMatrixAttribute SourceAttribute;
 8245    }
 8246
 8247    class OdinDrawer : OdinAttributeDrawer<OdinAttributeProxy> {
 8248      protected override void DrawPropertyLayout(GUIContent label) {
 8249
 8250        var rect = EditorGUILayout.GetControlRect();
 8251        var valueRect = EditorGUI.PrefixLabel(rect, label);
 8252        if (GUI.Button(valueRect, "Edit")) {
 8253          int[] values = (int[])this.Property.ValueEntry.WeakValues[0];
 8254
 8255          PopupWindow.Show(valueRect, new LayerMatrixPopup(label.text, (layerA, layerB) => {
 8256            if (layerA >= values.Length) {
 8257              return false;
 8258            }
 8259            return (values[layerA] & (1 << layerB)) != 0;
 8260          }, (layerA, layerB, val) => {
 8261            if (Mathf.Max(layerA, layerB) >= values.Length) {
 8262              Array.Resize(ref values, Mathf.Max(layerA, layerB) + 1);
 8263            }
 8264
 8265            if (val) {
 8266              values[layerA] |= (1 << layerB);
 8267              values[layerB] |= (1 << layerA);
 8268            } else {
 8269              values[layerA] &= ~(1 << layerB);
 8270              values[layerB] &= ~(1 << layerA);
 8271            }
 8272
 8273            // sync other values
 8274            for (int i = 1; i < this.Property.ValueEntry.ValueCount; ++i) {
 8275              this.Property.ValueEntry.WeakValues.ForceSetValue(i, values.Clone());
 8276            }
 8277
 8278            Property.MarkSerializationRootDirty();
 8279          }));
 8280        }
 8281      }
 8282    }
 8283  }
 8284}
 8285#endif
 8286
 8287#endregion
 8288
 8289
 8290#region FusionOdinAttributeConverterAttribute.cs
 8291
 8292namespace Fusion.Editor {
 8293  using System;
 8294
 8295  [AttributeUsage(AttributeTargets.Method)]
 8296  class FusionOdinAttributeConverterAttribute : Attribute {
 8297  }
 8298}
 8299
 8300#endregion
 8301
 8302
 8303#region FusionOdinAttributeProcessor.cs
 8304
 8305#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
 8306namespace Fusion.Editor {
 8307  using System;
 8308  using System.Collections.Generic;
 8309  using System.Linq;
 8310  using System.Reflection;
 8311  using UnityEngine;
 8312
 8313  internal class FusionOdinAttributeProcessor : Sirenix.OdinInspector.Editor.OdinAttributeProcessor {
 8314    public override void ProcessChildMemberAttributes(Sirenix.OdinInspector.Editor.InspectorProperty parentProperty, Mem
 8315      for (int i = 0; i < attributes.Count; ++i) {
 8316        var attribute = attributes[i];
 8317        if (attribute is PropertyAttribute) {
 8318
 8319          var drawerType = FusionEditorGUI.GetDrawerTypeIncludingWorkarounds(attribute);
 8320          if (drawerType != null) {
 8321
 8322            var method = drawerType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
 8323             .FirstOrDefault(x => x.IsDefined(typeof(FusionOdinAttributeConverterAttribute)));
 8324
 8325            if (method != null) {
 8326              var replacementAttributes = (System.Attribute[])method.Invoke(null, new object[] { member, attribute }) ??
 8327
 8328              attributes.RemoveAt(i);
 8329              FusionEditorLog.TraceInspector($"Replacing attribute {attribute.GetType().FullName} of {member.ToString()}
 8330
 8331              if (replacementAttributes.Length > 0) {
 8332                attributes.InsertRange(i, replacementAttributes);
 8333              }
 8334
 8335              i += replacementAttributes.Length - 1;
 8336              continue;
 8337            }
 8338          }
 8339
 8340          if (attribute is DecoratingPropertyAttribute) {
 8341            FusionEditorLog.Warn($"Unable to replace {nameof(DecoratingPropertyAttribute)}-derived attribute: {attribute
 8342            attributes.RemoveAt(i--);
 8343          }
 8344        }
 8345      }
 8346    }
 8347  }
 8348}
 8349#endif
 8350
 8351#endregion
 8352
 8353
 8354#region FusionOdinExtensions.cs
 8355
 8356#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
 8357namespace Fusion.Editor {
 8358  using System;
 8359  using System.Reflection;
 8360  using Sirenix.OdinInspector.Editor;
 8361  using UnityEditor;
 8362  using UnityEngine;
 8363
 8364  static class FusionOdinExtensions {
 8365    public static bool IsArrayElement(this InspectorProperty property, out int index) {
 8366      var propertyPath = property.UnityPropertyPath;
 8367
 8368      if (!propertyPath.EndsWith("]", StringComparison.Ordinal)) {
 8369        index = -1;
 8370        return false;
 8371      }
 8372
 8373      var indexStart = propertyPath.LastIndexOf("[", StringComparison.Ordinal);
 8374      if (indexStart < 0) {
 8375        index = -1;
 8376        return false;
 8377      }
 8378
 8379      index = int.Parse(propertyPath.Substring(indexStart + 1, propertyPath.Length - indexStart - 2));
 8380      return true;
 8381    }
 8382
 8383    public static bool IsArrayProperty(this InspectorProperty property) {
 8384      var memberType = property.Info.TypeOfValue;
 8385      if (!memberType.IsArrayOrList()) {
 8386        return false;
 8387      }
 8388
 8389      return true;
 8390    }
 8391
 8392    public static int GetValueDepth(this InspectorProperty property) {
 8393      int depth = 0;
 8394
 8395      var parent = property.GetValueParent();
 8396      while (parent?.IsTreeRoot == false) {
 8397        ++depth;
 8398        parent = parent.GetValueParent();
 8399      }
 8400
 8401      return depth;
 8402    }
 8403
 8404    public static InspectorProperty GetValueParent(this InspectorProperty property) {
 8405
 8406      var parent = property.Parent;
 8407      while (parent?.Info.PropertyType == PropertyType.Group) {
 8408        parent = parent.Parent;
 8409      }
 8410      return parent;
 8411    }
 8412
 8413    public static SerializedPropertyType GetUnityPropertyType(this InspectorProperty inspectorProperty) {
 8414      if (inspectorProperty == null) {
 8415        throw new ArgumentNullException(nameof(inspectorProperty));
 8416      }
 8417
 8418      var valueType = inspectorProperty.ValueEntry.TypeOfValue;
 8419
 8420      if (valueType == typeof(bool)) {
 8421        return SerializedPropertyType.Boolean;
 8422      } else if (valueType == typeof(int) || valueType == typeof(long) || valueType == typeof(short) || valueType == typ
 8423        return SerializedPropertyType.Integer;
 8424      } else if (valueType == typeof(float) || valueType == typeof(double)) {
 8425        return SerializedPropertyType.Float;
 8426      } else if (valueType == typeof(string)) {
 8427        return SerializedPropertyType.String;
 8428      } else if (valueType == typeof(Color)) {
 8429        return SerializedPropertyType.Color;
 8430      } else if (valueType == typeof(LayerMask)) {
 8431        return SerializedPropertyType.LayerMask;
 8432      } else if (valueType == typeof(Vector2)) {
 8433        return SerializedPropertyType.Vector2;
 8434      } else if (valueType == typeof(Vector3)) {
 8435        return SerializedPropertyType.Vector3;
 8436      } else if (valueType == typeof(Vector4)) {
 8437        return SerializedPropertyType.Vector4;
 8438      } else if (valueType == typeof(Vector2Int)) {
 8439        return SerializedPropertyType.Vector2Int;
 8440      } else if (valueType == typeof(Vector3Int)) {
 8441        return SerializedPropertyType.Vector3Int;
 8442      } else if (valueType == typeof(Rect)) {
 8443        return SerializedPropertyType.Rect;
 8444      } else if (valueType == typeof(RectInt)) {
 8445        return SerializedPropertyType.RectInt;
 8446      } else if (valueType == typeof(AnimationCurve)) {
 8447        return SerializedPropertyType.AnimationCurve;
 8448      } else if (valueType == typeof(Bounds)) {
 8449        return SerializedPropertyType.Bounds;
 8450      } else if (valueType == typeof(BoundsInt)) {
 8451        return SerializedPropertyType.BoundsInt;
 8452      } else if (valueType == typeof(Gradient)) {
 8453        return SerializedPropertyType.Gradient;
 8454      } else if (valueType == typeof(Quaternion)) {
 8455        return SerializedPropertyType.Quaternion;
 8456      } else if (valueType.IsEnum) {
 8457        return SerializedPropertyType.Enum;
 8458      } else if (typeof(UnityEngine.Object).IsAssignableFrom(valueType)) {
 8459        return SerializedPropertyType.ObjectReference;
 8460      } else if (valueType.IsArrayOrList()) {
 8461        return SerializedPropertyType.ArraySize;
 8462      }
 8463
 8464      return SerializedPropertyType.Generic;
 8465    }
 8466
 8467    public static InspectorProperty FindPropertyRelativeToParent(this InspectorProperty property, string path) {
 8468
 8469      InspectorProperty referenceProperty = property;
 8470
 8471      int parentIndex = 0;
 8472      do {
 8473        if (referenceProperty.GetValueParent() == null) {
 8474          return null;
 8475        }
 8476
 8477        referenceProperty = referenceProperty.GetValueParent();
 8478      } while (path[parentIndex++] == '^');
 8479
 8480      if (parentIndex > 1) {
 8481        path = path.Substring(parentIndex - 1);
 8482      }
 8483
 8484      var parts = path.Split('.');
 8485      if (parts.Length == 0) {
 8486        return null;
 8487      }
 8488
 8489      foreach (var part in parts) {
 8490        var child = referenceProperty.Children[part];
 8491        if (child != null) {
 8492          referenceProperty = child;
 8493        } else {
 8494          return null;
 8495        }
 8496      }
 8497
 8498      return referenceProperty;
 8499    }
 8500
 8501    public static (FusionPropertyDrawerMetaAttribute, Attribute) GetNextPropertyDrawerMetaAttribute(this InspectorProper
 8502
 8503      var attributeIndex = referenceAttribute == null ? -1 : property.Attributes.IndexOf(referenceAttribute);
 8504
 8505      for (int i = attributeIndex + 1; i < property.Attributes.Count; ++i) {
 8506        var otherAttribute = property.Attributes[i];
 8507        if (otherAttribute is DrawerPropertyAttribute == false) {
 8508          continue;
 8509        }
 8510
 8511        var attributeDrawerType = FusionEditorGUI.GetDrawerTypeIncludingWorkarounds(otherAttribute);
 8512        if (attributeDrawerType == null) {
 8513          continue;
 8514        }
 8515
 8516        var meta = attributeDrawerType.GetCustomAttribute<FusionPropertyDrawerMetaAttribute>();
 8517        if (meta != null) {
 8518          return (meta, otherAttribute);
 8519        }
 8520      }
 8521
 8522
 8523      var propertyDrawerType = UnityInternal.ScriptAttributeUtility.GetDrawerTypeForType(property.ValueEntry.TypeOfValue
 8524
 8525      if (propertyDrawerType != null) {
 8526        var meta = propertyDrawerType.GetCustomAttribute<FusionPropertyDrawerMetaAttribute>();
 8527        if (meta != null) {
 8528          return (meta, null);
 8529        }
 8530      }
 8531
 8532      return (null, null);
 8533    }
 8534  }
 8535}
 8536#endif
 8537
 8538#endregion
 8539
 8540
 8541#region ReadOnlyAttributeDrawer.Odin.cs
 8542
 8543namespace Fusion.Editor {
 8544  partial class ReadOnlyAttributeDrawer {
 8545#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
 8546    [FusionOdinAttributeConverter]
 8547    static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, ReadOnlyAttribute attribu
 8548      if (attribute.InEditMode && attribute.InPlayMode) {
 8549        return new[] { new Sirenix.OdinInspector.ReadOnlyAttribute() };
 8550      }
 8551      if (attribute.InEditMode) {
 8552        return new[] { new Sirenix.OdinInspector.DisableInEditorModeAttribute() };
 8553      }
 8554      if (attribute.InPlayMode) {
 8555        return new[] { new Sirenix.OdinInspector.DisableInPlayModeAttribute() };
 8556      }
 8557      return System.Array.Empty<System.Attribute>();
 8558    }
 8559#endif
 8560  }
 8561}
 8562
 8563#endregion
 8564
 8565
 8566#region SerializeReferenceTypePickerDrawer.Odin.cs
 8567
 8568#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
 8569namespace Fusion.Editor {
 8570  using System;
 8571
 8572  partial class SerializeReferenceTypePickerAttributeDrawer {
 8573    [FusionOdinAttributeConverter]
 8574      static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, SerializeReferenceTypeP
 8575        return Array.Empty<System.Attribute>();
 8576      }
 8577  }
 8578}
 8579#endif
 8580
 8581#endregion
 8582
 8583
 8584#region UnitAttributeDrawer.Odin.cs
 8585
 8586#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
 8587namespace Fusion.Editor {
 8588  using System;
 8589  using UnityEditor;
 8590  using UnityEngine;
 8591
 8592  partial class UnitAttributeDrawer {
 8593
 8594    [FusionOdinAttributeConverter]
 8595    static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, UnitAttribute attribute) 
 8596      return new[] { new OdinAttributeProxy() { SourceAttribute = attribute } };
 8597    }
 8598
 8599    class OdinAttributeProxy : Attribute {
 8600      public UnitAttribute SourceAttribute;
 8601    }
 8602
 8603    class OdinUnitAttributeDrawer :  Sirenix.OdinInspector.Editor.OdinAttributeDrawer<OdinAttributeProxy> {
 8604      private GUIContent _label;
 8605      private Rect       _lastRect;
 8606
 8607      protected override bool CanDrawAttributeProperty(Sirenix.OdinInspector.Editor.InspectorProperty property) {
 8608
 8609        for (Attribute attrib = null;;) {
 8610          var (meta, nextAttribute) = property.GetNextPropertyDrawerMetaAttribute(attrib);
 8611          attrib                    = nextAttribute;
 8612          if (meta?.HandlesUnits == true) {
 8613            if (attrib is OdinAttributeProxy == false) {
 8614              return false;
 8615            }
 8616          }
 8617
 8618          if (meta == null || attrib == null) {
 8619            break;
 8620          }
 8621        }
 8622
 8623        switch (property.GetUnityPropertyType()) {
 8624          case SerializedPropertyType.ArraySize:
 8625            return false;
 8626          default:
 8627            return true;
 8628        }
 8629      }
 8630
 8631      protected sealed override void DrawPropertyLayout(GUIContent label) {
 8632
 8633        using (new EditorGUILayout.VerticalScope()) {
 8634          this.CallNextDrawer(label);
 8635        }
 8636
 8637        if (Event.current.type == EventType.Repaint) {
 8638          _lastRect = GUILayoutUtility.GetLastRect();
 8639        }
 8640
 8641        if (_lastRect.width > 1 && _lastRect.height > 1) {
 8642          _label      ??= new GUIContent();
 8643          _label.text =   UnitToLabel(this.Attribute.SourceAttribute.Unit);
 8644          DrawUnitOverlay(_lastRect, _label, Property.GetUnityPropertyType(), false, odinStyle: true);
 8645        }
 8646      }
 8647    }
 8648  }
 8649}
 8650#endif
 8651
 8652#endregion
 8653
 8654
 8655#region WarnIfAttributeDrawer.Odin.cs
 8656
 8657#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
 8658namespace Fusion.Editor {
 8659  using UnityEngine;
 8660
 8661  partial class WarnIfAttributeDrawer {
 8662
 8663    [FusionOdinAttributeConverter]
 8664    static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, WarnIfAttribute attribute
 8665      return new[] { new OdinAttributeProxy() { SourceAttribute = attribute } };
 8666    }
 8667
 8668    class OdinAttributeProxy : OdinProxyAttributeBase {
 8669    }
 8670
 8671    class OdinDrawer : OdinDrawerBase<OdinAttributeProxy> {
 8672      protected override void DrawPropertyLayout(GUIContent label, bool allPassed, bool anyPassed) {
 8673        var attribute = (WarnIfAttribute)Attribute.SourceAttribute;
 8674
 8675        base.CallNextDrawer(label);
 8676
 8677        if (anyPassed) {
 8678          using (new FusionEditorGUI.WarningScope(attribute.Message)) {
 8679          }
 8680        }
 8681      }
 8682    }
 8683  }
 8684}
 8685#endif
 8686
 8687#endregion
 8688
 8689
 8690#region ArrayLengthAttributeDrawer.cs
 8691
 8692namespace Fusion.Editor {
 8693  using UnityEditor;
 8694  using UnityEngine;
 8695
 8696  internal partial class ArrayLengthAttributeDrawer : DecoratingPropertyAttributeDrawer, INonApplicableOnArrayElements {
 8697
 8698    private GUIStyle _style;
 8699
 8700    private GUIStyle GetStyle() {
 8701      if (_style == null) {
 8702        _style                  = new GUIStyle(EditorStyles.miniLabel);
 8703        _style.alignment        = TextAnchor.MiddleRight;
 8704        _style.contentOffset    = new Vector2(-2, 0);
 8705        _style.normal.textColor = EditorGUIUtility.isProSkin ? new Color(255f / 255f, 221 / 255f, 0 / 255f, 1f) : Color.
 8706      }
 8707
 8708      return _style;
 8709    }
 8710
 8711    protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 8712
 8713      base.OnGUIInternal(position, property, label);
 8714      if (!property.isArray) {
 8715        return;
 8716      }
 8717
 8718      var overlayRect = position;
 8719      overlayRect.height = EditorGUIUtility.singleLineHeight;
 8720
 8721      var attrib = (ArrayLengthAttribute)attribute;
 8722
 8723      // draw length overlay
 8724      GUI.Label(overlayRect, $"[{attrib.MaxLength}]", GetStyle());
 8725
 8726      if (property.arraySize > attrib.MaxLength) {
 8727        property.arraySize = attrib.MaxLength;
 8728        property.serializedObject.ApplyModifiedProperties();
 8729      } else if (property.arraySize < attrib.MinLength) {
 8730        property.arraySize = attrib.MinLength;
 8731        property.serializedObject.ApplyModifiedProperties();
 8732      }
 8733    }
 8734  }
 8735
 8736  [CustomPropertyDrawer(typeof(ArrayLengthAttribute))]
 8737  [RedirectCustomPropertyDrawer(typeof(ArrayLengthAttribute), typeof(ArrayLengthAttributeDrawer))]
 8738  partial class PropertyDrawerForArrayWorkaround {
 8739  }
 8740}
 8741
 8742#endregion
 8743
 8744
 8745#region AssemblyNameAttributeDrawer.cs
 8746
 8747namespace Fusion.Editor {
 8748  using System;
 8749  using System.Collections.Generic;
 8750  using System.IO;
 8751  using System.Linq;
 8752  using UnityEditor;
 8753  using UnityEngine;
 8754
 8755  [CustomPropertyDrawer(typeof(AssemblyNameAttribute))]
 8756  internal class AssemblyNameAttributeDrawer : PropertyDrawerWithErrorHandling {
 8757    const float DropdownWidth = 20.0f;
 8758
 8759    static GUIContent DropdownContent = new GUIContent("");
 8760
 8761    string _lastCheckedAssemblyName;
 8762
 8763    [Flags]
 8764    enum AsmDefType {
 8765      Predefined = 1 << 0,
 8766      InPackages = 1 << 1,
 8767      InAssets   = 1 << 2,
 8768      Editor     = 1 << 3,
 8769      Runtime    = 1 << 4,
 8770      All        = Predefined | InPackages | InAssets | Editor | Runtime,
 8771    }
 8772
 8773    Dictionary<string, AssemblyInfo> _allAssemblies;
 8774
 8775    protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 8776      var  assemblyName = property.stringValue;
 8777      bool notFound     = false;
 8778
 8779      if (!string.IsNullOrEmpty(assemblyName)) {
 8780        if (_allAssemblies == null) {
 8781          _allAssemblies = GetAssemblies(AsmDefType.All).ToDictionary(x => x.Name, x => x);
 8782        }
 8783
 8784        if (!_allAssemblies.TryGetValue(assemblyName, out var assemblyInfo)) {
 8785          SetInfo($"Assembly not found: {assemblyName}");
 8786          notFound = true;
 8787        } else if (((AssemblyNameAttribute)attribute).RequiresUnsafeCode && !assemblyInfo.AllowUnsafeCode) {
 8788          if (assemblyInfo.IsPredefined) {
 8789            SetError($"Predefined assemblies need 'Allow Unsafe Code' enabled in Player Settings");
 8790          } else {
 8791            SetError($"Assembly does not allow unsafe code");
 8792          }
 8793        }
 8794      }
 8795
 8796      using (new FusionEditorGUI.PropertyScope(position, label, property)) {
 8797        EditorGUI.BeginChangeCheck();
 8798
 8799        assemblyName = EditorGUI.TextField(new Rect(position) { xMax = position.xMax - DropdownWidth },
 8800          label,
 8801          assemblyName,
 8802          notFound ?
 8803            new GUIStyle(EditorStyles.textField) {
 8804              fontStyle = FontStyle.Italic,
 8805              normal    = new GUIStyleState() { textColor = Color.gray }
 8806            } : EditorStyles.textField
 8807        );
 8808
 8809        var dropdownRect = EditorGUI.IndentedRect(new Rect(position) {
 8810          xMin = position.xMax - DropdownWidth
 8811        });
 8812
 8813        if (EditorGUI.DropdownButton(dropdownRect, DropdownContent, FocusType.Passive)) {
 8814          GenericMenu.MenuFunction2 onClicked = (userData) => {
 8815            property.stringValue = (string)userData;
 8816            property.serializedObject.ApplyModifiedProperties();
 8817            UnityInternal.EditorGUI.EndEditingActiveTextField();
 8818            ClearError(property);
 8819          };
 8820
 8821          var menu = new GenericMenu();
 8822
 8823          foreach (var (flag, prefix) in new[] {
 8824                     (AsmDefType.Editor, "Editor/"),
 8825                     (AsmDefType.Runtime, "")
 8826                   }) {
 8827            if (menu.GetItemCount() != 0) {
 8828              menu.AddSeparator(prefix);
 8829            }
 8830
 8831            foreach (var asm in GetAssemblies(flag | AsmDefType.InPackages)) {
 8832              menu.AddItem(new GUIContent($"{prefix}Packages/{asm.Name}"), string.Equals(asm.Name, assemblyName, StringC
 8833            }
 8834
 8835            menu.AddSeparator(prefix);
 8836
 8837            foreach (var asm in GetAssemblies(flag | AsmDefType.InAssets | AsmDefType.Predefined)) {
 8838              menu.AddItem(new GUIContent($"{prefix}{asm.Name}"), string.Equals(asm.Name, assemblyName, StringComparison
 8839            }
 8840          }
 8841
 8842          menu.DropDown(dropdownRect);
 8843        }
 8844
 8845        if (EditorGUI.EndChangeCheck()) {
 8846          property.stringValue = assemblyName;
 8847          property.serializedObject.ApplyModifiedProperties();
 8848          base.ClearError();
 8849        }
 8850      }
 8851    }
 8852
 8853    static IEnumerable<AssemblyInfo> GetAssemblies(AsmDefType types) {
 8854      var result = new Dictionary<string, AsmDefData>(StringComparer.OrdinalIgnoreCase);
 8855
 8856      if (types.HasFlag(AsmDefType.Predefined)) {
 8857        if (types.HasFlag(AsmDefType.Runtime)) {
 8858          yield return new AssemblyInfo("Assembly-CSharp-firstpass", PlayerSettings.allowUnsafeCode, true);
 8859          yield return new AssemblyInfo("Assembly-CSharp", PlayerSettings.allowUnsafeCode, true);
 8860        }
 8861
 8862        if (types.HasFlag(AsmDefType.Editor)) {
 8863          yield return new AssemblyInfo("Assembly-CSharp-Editor-firstpass", PlayerSettings.allowUnsafeCode, true);
 8864          yield return new AssemblyInfo("Assembly-CSharp-Editor", PlayerSettings.allowUnsafeCode, true);
 8865        }
 8866      }
 8867
 8868      if (types.HasFlag(AsmDefType.InAssets) || types.HasFlag(AsmDefType.InPackages)) {
 8869        var query = AssetDatabase.FindAssets("t:asmdef")
 8870         .Select(x => AssetDatabase.GUIDToAssetPath(x))
 8871         .Where(x => {
 8872            if (types.HasFlag(AsmDefType.InAssets) && x.StartsWith("Assets/")) {
 8873              return true;
 8874            } else if (types.HasFlag(AsmDefType.InPackages) && x.StartsWith("Packages/")) {
 8875              return true;
 8876            } else {
 8877              return false;
 8878            }
 8879         })
 8880         .Select(x => JsonUtility.FromJson<AsmDefData>(File.ReadAllText(x)))
 8881         .Where(x => {
 8882           bool editorOnly = x.includePlatforms.Length == 1 && x.includePlatforms[0] == "Editor";
 8883           if (types.HasFlag(AsmDefType.Runtime) && !editorOnly) {
 8884             return true;
 8885           } else if (types.HasFlag(AsmDefType.Editor) && editorOnly) {
 8886             return true;
 8887           } else {
 8888             return false;
 8889           }
 8890         });
 8891
 8892        foreach (var asmdef in query) {
 8893          yield return new AssemblyInfo(asmdef.name, asmdef.allowUnsafeCode, false);
 8894        }
 8895      }
 8896    }
 8897
 8898    [Serializable]
 8899    private class AsmDefData {
 8900      public string[] includePlatforms = Array.Empty<string>();
 8901      public string   name             = string.Empty;
 8902      public bool     allowUnsafeCode;
 8903    }
 8904
 8905    private struct AssemblyInfo {
 8906      public string Name;
 8907      public bool   AllowUnsafeCode;
 8908      public bool   IsPredefined;
 8909
 8910      public AssemblyInfo(string name, bool allowUnsafeCode, bool isPredefined) {
 8911        Name           = name;
 8912        AllowUnsafeCode = allowUnsafeCode;
 8913        IsPredefined   = isPredefined;
 8914      }
 8915    }
 8916  }
 8917}
 8918
 8919#endregion
 8920
 8921
 8922#region BinaryDataAttributeDrawer.cs
 8923
 8924namespace Fusion.Editor {
 8925  using UnityEditor;
 8926  using UnityEngine;
 8927
 8928  internal partial class BinaryDataAttributeDrawer : PropertyDrawerWithErrorHandling, INonApplicableOnArrayElements {
 8929
 8930    private int           MaxLines  = 16;
 8931    private RawDataDrawer _drawer   = new RawDataDrawer();
 8932
 8933    protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 8934      using (new FusionEditorGUI.PropertyScope(position, label, property)) {
 8935        bool wasExpanded = property.isExpanded;
 8936
 8937        var foldoutPosition = new Rect(position) { height = EditorGUIUtility.singleLineHeight };
 8938        property.isExpanded = EditorGUI.Foldout(foldoutPosition, property.isExpanded, label);
 8939
 8940        if (property.hasMultipleDifferentValues) {
 8941          FusionEditorGUI.Overlay(foldoutPosition, $"---");
 8942        } else {
 8943          FusionEditorGUI.Overlay(foldoutPosition, $"{property.arraySize}");
 8944        }
 8945
 8946        if (!wasExpanded) {
 8947          return;
 8948        }
 8949
 8950        position.yMin += foldoutPosition.height + EditorGUIUtility.standardVerticalSpacing;
 8951        using (new FusionEditorGUI.EnabledScope(true)) {
 8952          _drawer.Draw(GUIContent.none, position);
 8953        }
 8954      }
 8955    }
 8956
 8957    public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
 8958
 8959      if (!property.isExpanded) {
 8960        return EditorGUIUtility.singleLineHeight;
 8961      }
 8962
 8963      _drawer.Refresh(property);
 8964
 8965      // space for scrollbar and indent
 8966      var width  = UnityInternal.EditorGUIUtility.contextWidth - 32.0f;
 8967      var height = _drawer.GetHeight(width);
 8968
 8969      return EditorGUIUtility.singleLineHeight +
 8970        EditorGUIUtility.standardVerticalSpacing +
 8971        Mathf.Min(FusionEditorGUI.GetLinesHeight(MaxLines), height);
 8972    }
 8973  }
 8974
 8975
 8976  [CustomPropertyDrawer(typeof(BinaryDataAttribute))]
 8977  [RedirectCustomPropertyDrawer(typeof(BinaryDataAttribute), typeof(BinaryDataAttributeDrawer))]
 8978  partial class PropertyDrawerForArrayWorkaround {
 8979  }
 8980}
 8981
 8982#endregion
 8983
 8984
 8985#region BitSetAttributeDrawer.cs
 8986
 8987// namespace Fusion.Editor {
 8988//   using System;
 8989//   using UnityEditor;
 8990//   using UnityEngine;
 8991//
 8992//   [CustomPropertyDrawer(typeof(BitSetAttribute))]
 8993//   public class BitSetAttributeDrawer : PropertyDrawer {
 8994//
 8995//     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
 8996//
 8997//       if (property.IsArrayElement()) {
 8998//         throw new NotSupportedException();
 8999//       }
 9000//
 9001//       var longValue = property.longValue;
 9002//
 9003//       int bitStart = 0;
 9004//       int bitEnd   = ((BitSetAttribute)attribute).BitCount;
 9005//
 9006//       using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out var valueRect)) {
 9007//         var pos = valueRect;
 9008//
 9009//         DrawAndMeasureLabel(valueRect, bitStart, FusionEditorSkin.instance.MiniLabelLowerRight);
 9010//         DrawAndMeasureLabel(valueRect, bitEnd, FusionEditorSkin.instance.MiniLabelLowerLeft);
 9011//
 9012//         var tmpContent = new GUIContent();
 9013//         tmpContent.text = $"{bitStart}";
 9014//         var bitStartSize = EditorStyles.miniLabel.CalcSize(tmpContent);
 9015//
 9016//
 9017//         tmpContent.text = $"{bitEnd}";
 9018//         var bitEndSize = EditorStyles.miniLabel.CalcSize(tmpContent);
 9019//         valueRect.width = bitEndSize.x;
 9020//         GUI.Label(valueRect, tmpContent, EditorStyles.miniLabel);
 9021//         valueRect.x += bitEndSize.x;
 9022//         var availableWidth = valueRect.width - bitStartSize.x - bitEndSize.x;
 9023//
 9024//
 9025//         // how may per one line?
 9026//         const float ToggleWidth = 15.0f;
 9027//
 9028//         valueRect.width = ToggleWidth;
 9029//         for (int i = 0; i < 16; ++i) {
 9030//           EditorGUI.Toggle(valueRect, false);
 9031//           valueRect.x += ToggleWidth;
 9032//         }
 9033//       }
 9034//
 9035//       float DrawAndMeasureLabel(Rect position, int label, GUIStyle style) {
 9036//         var tmpContent = new GUIContent($"{bitEnd}");
 9037//         var contentSize = style.CalcSize(tmpContent);
 9038//         GUI.Label(position, tmpContent, style);
 9039//         return contentSize.x;
 9040//       }
 9041//
 9042//       //base.OnGUI(position, property, label);
 9043//     }
 9044//   }
 9045// }
 9046
 9047#endregion
 9048
 9049
 9050#region DecoratingPropertyAttributeDrawer.cs
 9051
 9052//#define FUSION_EDITOR_TRACE
 9053namespace Fusion.Editor {
 9054  using System;
 9055  using System.Collections.Generic;
 9056  using System.Diagnostics;
 9057  using System.Linq;
 9058  using UnityEditor;
 9059  using UnityEngine;
 9060
 9061  internal abstract class DecoratingPropertyAttributeDrawer : PropertyDrawer {
 9062    private bool _isLastDrawer;
 9063    private int _nestingLevel;
 9064
 9065    /// <summary>
 9066    ///   The drawer that's been chosen by Unity; its job is to
 9067    ///   iterate all ForwardingPropertyDrawerBase drawers
 9068    ///   that'd be created had Unity 2020.3 supported multiple
 9069    ///   property drawers - including self.
 9070    /// </summary>
 9071    protected DecoratingPropertyAttributeDrawer MainDrawer { get; private set; }
 9072
 9073    public List<DecoratingPropertyAttributeDrawer> PropertyDrawers { get; private set; }
 9074
 9075    public PropertyDrawer NextDrawer { get; private set; }
 9076
 9077    public DecoratingPropertyAttributeDrawer() {
 9078      TraceField("constructor");
 9079    }
 9080
 9081    [Obsolete("Derived classes should override and call OnGUIInternal", true)]
 9082#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member
 9083    public sealed override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
 9084#pragma warning restore CS0809 // Obsolete member overrides non-obsolete member
 9085      TraceField($"OnGUI({position}, {property.propertyPath}, {label})");
 9086      EnsureInitialized(property);
 9087      FusionEditorLog.Assert(MainDrawer == this);
 9088      FusionEditorLog.Assert(PropertyDrawers != null);
 9089      FusionEditorLog.Assert(PropertyDrawers.Count > 0);
 9090      PropertyDrawers[0].InvokeOnGUIInternal(position, property, label);
 9091    }
 9092
 9093    [Obsolete("Derived classes should override and call GetPropertyHeightInternal", true)]
 9094#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member
 9095    public sealed override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
 9096#pragma warning restore CS0809 // Obsolete member overrides non-obsolete member
 9097      TraceField($"GetPropertyHeight({property.propertyPath}, {label})");
 9098      EnsureInitialized(property);
 9099      FusionEditorLog.Assert(MainDrawer == this);
 9100      FusionEditorLog.Assert(PropertyDrawers != null);
 9101      FusionEditorLog.Assert(PropertyDrawers.Count > 0);
 9102      return PropertyDrawers[0].InvokeGetPropertyHeightInternal(property, label);
 9103    }
 9104
 9105    protected virtual float GetPropertyHeightInternal(SerializedProperty property, GUIContent label) {
 9106      FusionEditorLog.Assert(MainDrawer != null);
 9107      return MainDrawer.InvokeGetPropertyHeightOnNextDrawer(this, property, label);
 9108    }
 9109
 9110    protected virtual void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 9111      TraceField($"OnGUIInternal({position}, {property.propertyPath}, {label})");
 9112      FusionEditorLog.Assert(MainDrawer != null);
 9113
 9114      FusionEditorLog.Assert(_nestingLevel == 0, $"{property.propertyPath} {GetType().FullName}");
 9115      _nestingLevel++;
 9116      try {
 9117        MainDrawer.InvokeOnGUIOnNextDrawer(this, position, property, label);
 9118      } finally {
 9119        _nestingLevel--;
 9120      }
 9121    }
 9122
 9123    private void InvokeOnGUIOnNextDrawer(DecoratingPropertyAttributeDrawer current, Rect position, SerializedProperty pr
 9124      FusionEditorLog.Assert(MainDrawer == this);
 9125      var index = PropertyDrawers.IndexOf(current);
 9126      if (index < PropertyDrawers.Count - 1) {
 9127        PropertyDrawers[index + 1].InvokeOnGUIInternal(position, prop, label);
 9128      } else {
 9129        if (NextDrawer != null) {
 9130          NextDrawer.OnGUI(position, prop, label);
 9131        } else {
 9132          FusionEditorGUI.ForwardPropertyField(position, prop, label, prop.IsArrayProperty() ? true : prop.isExpanded, _
 9133        }
 9134      }
 9135    }
 9136
 9137    private void InvokeOnGUIInternal(Rect position, SerializedProperty prop, GUIContent label) {
 9138      if (prop.IsArrayElement() && this is INonApplicableOnArrayElements) {
 9139        MainDrawer.InvokeOnGUIOnNextDrawer(this, position, prop, label);
 9140      } else {
 9141        OnGUIInternal(position, prop, label);
 9142      }
 9143    }
 9144
 9145    private float InvokeGetPropertyHeightOnNextDrawer(DecoratingPropertyAttributeDrawer current, SerializedProperty prop
 9146      FusionEditorLog.Assert(MainDrawer == this);
 9147      var index = PropertyDrawers.IndexOf(current);
 9148      if (index < PropertyDrawers.Count - 1) {
 9149        return PropertyDrawers[index + 1].InvokeGetPropertyHeightInternal(prop, label);
 9150      }
 9151
 9152      return NextDrawer?.GetPropertyHeight(prop, label) ?? EditorGUI.GetPropertyHeight(prop, label);
 9153    }
 9154
 9155    private float InvokeGetPropertyHeightInternal(SerializedProperty prop, GUIContent label) {
 9156      if (prop.IsArrayElement() && this is INonApplicableOnArrayElements) {
 9157        return MainDrawer.InvokeGetPropertyHeightOnNextDrawer(this, prop, label);
 9158      } else {
 9159        return GetPropertyHeightInternal(prop, label);
 9160      }
 9161    }
 9162
 9163    protected virtual bool EnsureInitialized(SerializedProperty property) {
 9164      if (MainDrawer != null || PropertyDrawers != null) {
 9165        return false;
 9166      }
 9167
 9168      if (fieldInfo == null) {
 9169        // this might happen if this drawer is created dynamically
 9170        var field = UnityInternal.ScriptAttributeUtility.GetFieldInfoFromProperty(property, out _);
 9171        FusionEditorLog.Assert(field != null, $"Could not find field for property {property.propertyPath} of type {prope
 9172        UnityInternal.PropertyDrawer.SetFieldInfo(this, field);
 9173      }
 9174
 9175      FusionEditorLog.Assert(attribute != null);
 9176      FusionEditorLog.Assert(attribute is DecoratingPropertyAttribute, $"Expected attribute to be of type {nameof(Decora
 9177
 9178      PropertyDrawers = new List<DecoratingPropertyAttributeDrawer>();
 9179      MainDrawer     = this;
 9180      NextDrawer      = null;
 9181
 9182      var isLastDrawer = false;
 9183      var foundSelf    = false;
 9184
 9185      var fieldAttributes = fieldInfo != null ? UnityInternal.ScriptAttributeUtility.GetFieldAttributes(fieldInfo) : nul
 9186
 9187      if (fieldAttributes != null) {
 9188        FusionEditorLog.Assert(fieldAttributes.OrderBy(x => x.order).SequenceEqual(fieldAttributes), "Expected field att
 9189        FusionEditorLog.Assert(fieldAttributes.Count > 0);
 9190
 9191        for (var i = 0; i < fieldAttributes.Count; ++i) {
 9192          var fieldAttribute = fieldAttributes[i];
 9193
 9194          var attributeDrawerType = UnityInternal.ScriptAttributeUtility.GetDrawerTypeForPropertyAndType(property, field
 9195          if (attributeDrawerType == null) {
 9196            TraceField($"No drawer for {attributeDrawerType}");
 9197            continue;
 9198          }
 9199
 9200          if (attributeDrawerType == typeof(PropertyDrawerForArrayWorkaround)) {
 9201            attributeDrawerType = PropertyDrawerForArrayWorkaround.GetDrawerType(fieldAttribute.GetType());
 9202          }
 9203
 9204          if (attributeDrawerType.IsSubclassOf(typeof(DecoratorDrawer))) {
 9205            // decorators are their own thing
 9206            continue;
 9207          }
 9208
 9209          if (property.IsArrayElement() && attributeDrawerType.GetInterface(typeof(INonApplicableOnArrayElements).FullNa
 9210            // skip drawers that are not meant to be used on array elements
 9211            continue;
 9212          }
 9213
 9214          FusionEditorLog.Assert(attributeDrawerType.IsSubclassOf(typeof(PropertyDrawer)));
 9215
 9216          if (!foundSelf && fieldAttribute.Equals(attribute)) {
 9217            // self
 9218            PropertyDrawers.Add(this);
 9219            foundSelf    = true;
 9220            isLastDrawer = true;
 9221            TraceField($"Found self at {i} ({this})");
 9222            continue;
 9223          }
 9224
 9225          isLastDrawer = false;
 9226        }
 9227      }
 9228
 9229      if (!foundSelf) {
 9230        TraceField("Force-adding self");
 9231        PropertyDrawers.Add(this);
 9232      }
 9233
 9234      if (NextDrawer == null && isLastDrawer && fieldInfo != null) {
 9235        // try creating type drawer instead
 9236        var fieldType      = fieldInfo.FieldType;
 9237        if (property.IsArrayElement()) {
 9238          fieldType = fieldType.GetUnityLeafType();
 9239        }
 9240
 9241        var typeDrawerType = UnityInternal.ScriptAttributeUtility.GetDrawerTypeForPropertyAndType(property, fieldType);
 9242        if (typeDrawerType != null) {
 9243          var drawer = (PropertyDrawer)Activator.CreateInstance(typeDrawerType);
 9244          UnityInternal.PropertyDrawer.SetFieldInfo(drawer, fieldInfo);
 9245          TraceField($"Found final drawer is type drawer ({drawer})");
 9246          NextDrawer = drawer;
 9247        }
 9248      }
 9249
 9250      if (isLastDrawer) {
 9251        _isLastDrawer = true;
 9252      }
 9253
 9254      return true;
 9255    }
 9256
 9257    internal void InitInjected(PropertyDrawer next) {
 9258      MainDrawer = this;
 9259      PropertyDrawers = new List<DecoratingPropertyAttributeDrawer> {
 9260        this
 9261      };
 9262      NextDrawer = next;
 9263    }
 9264
 9265    public PropertyDrawer GetNextDrawer(SerializedProperty property) {
 9266      if (NextDrawer != null) {
 9267        return NextDrawer;
 9268      }
 9269
 9270      var handler = UnityInternal.ScriptAttributeUtility.propertyHandlerCache.GetHandler(property);
 9271      var drawers = handler.m_PropertyDrawers;
 9272      var index   = drawers.IndexOf(this);
 9273      if (index >= 0 && index < drawers.Count - 1) {
 9274        return drawers[index + 1];
 9275      }
 9276
 9277      return null;
 9278    }
 9279
 9280
 9281    [Conditional("FUSION_EDITOR_TRACE")]
 9282    private void TraceField(string message) {
 9283      FusionEditorLog.TraceInspector($"[{GetType().FullName}] [{GetHashCode():X8}] [{fieldInfo?.DeclaringType.Name}.{fie
 9284    }
 9285  }
 9286}
 9287
 9288#endregion
 9289
 9290
 9291#region DisplayAsEnumAttributeDrawer.cs
 9292
 9293namespace Fusion.Editor {
 9294  using System;
 9295  using System.Collections.Generic;
 9296  using System.Reflection;
 9297  using UnityEditor;
 9298  using UnityEngine;
 9299
 9300  [CustomPropertyDrawer(typeof(DisplayAsEnumAttribute))]
 9301  internal class DisplayAsEnumAttributeDrawer : PropertyDrawerWithErrorHandling {
 9302
 9303    private EnumDrawer                 _enumDrawer;
 9304    private Dictionary<(Type, string), Func<object, Type>> _cachedGetters = new Dictionary<(Type, string), Func<object, 
 9305
 9306    protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 9307      var attr     = (DisplayAsEnumAttribute)attribute;
 9308      var enumType = attr.EnumType;
 9309
 9310      if (enumType == null && !string.IsNullOrEmpty(attr.EnumTypeMemberName)) {
 9311
 9312        var objType = property.serializedObject.targetObject.GetType();
 9313        if (!_cachedGetters.TryGetValue((objType, attr.EnumTypeMemberName), out var getter)) {
 9314          // maybe this is a top-level property then and we can use reflection?
 9315          if (property.depth != 0) {
 9316            FusionEditorLog.ErrorInspector($"Can't get enum type for {property.propertyPath}: non-SerializedProperty che
 9317          } else {
 9318            try {
 9319              getter = objType.CreateGetter<Type>(attr.EnumTypeMemberName, BindingFlags.Public | BindingFlags.NonPublic 
 9320            } catch (Exception e) {
 9321              FusionEditorLog.ErrorInspector($"Can't get enum type for {property.propertyPath}: unable to create getter 
 9322            }
 9323          }
 9324
 9325          _cachedGetters.Add((objType, attr.EnumTypeMemberName), getter);
 9326        }
 9327
 9328        enumType = getter(property.serializedObject.targetObject);
 9329      }
 9330
 9331      using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out var valueRect)) {
 9332        if (enumType == null) {
 9333          SetError($"Unable to get enum type for {property.propertyPath}");
 9334        } else if (!enumType.IsEnum) {
 9335          SetError($"Type {enumType} is not an enum type");
 9336        } else {
 9337          ClearError();
 9338          _enumDrawer.Draw(valueRect, property, enumType, true);
 9339        }
 9340      }
 9341    }
 9342  }
 9343}
 9344
 9345#endregion
 9346
 9347
 9348#region DisplayNameAttributeDrawer.cs
 9349
 9350namespace Fusion.Editor {
 9351  using UnityEditor;
 9352  using UnityEngine;
 9353
 9354  //[CustomPropertyDrawer(typeof(DisplayNameAttribute))]
 9355  internal class DisplayNameAttributeDrawer : DecoratingPropertyAttributeDrawer, INonApplicableOnArrayElements {
 9356    private GUIContent _label = new GUIContent();
 9357
 9358    protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 9359      if (((DisplayNameAttribute)attribute).Name == null) {
 9360        base.OnGUIInternal(position, property, label);
 9361        return;
 9362      }
 9363      if (label.text == string.Empty && label.image == null || property.IsArrayElement()) {
 9364        base.OnGUIInternal(position, property, label);
 9365        return;
 9366      }
 9367      _label.text    = ((DisplayNameAttribute)attribute).Name;
 9368      _label.image   = label.image;
 9369      _label.tooltip = label.tooltip;
 9370      base.OnGUIInternal(position, property, _label);
 9371    }
 9372
 9373#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
 9374    [FusionOdinAttributeConverter]
 9375    static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, DisplayNameAttribute attr
 9376      return new[] { new Sirenix.OdinInspector.LabelTextAttribute(attribute.Name) };
 9377    }
 9378#endif
 9379  }
 9380
 9381  [CustomPropertyDrawer(typeof(DisplayNameAttribute))]
 9382  [RedirectCustomPropertyDrawer(typeof(DisplayNameAttribute), typeof(DisplayNameAttributeDrawer))]
 9383  partial class PropertyDrawerForArrayWorkaround {
 9384  }
 9385}
 9386
 9387#endregion
 9388
 9389
 9390#region DoIfAttributeDrawer.cs
 9391
 9392namespace Fusion.Editor {
 9393  using System;
 9394  using System.Collections.Generic;
 9395  using System.Reflection;
 9396  using UnityEditor;
 9397
 9398  internal abstract partial class DoIfAttributeDrawer : DecoratingPropertyAttributeDrawer, INonApplicableOnArrayElements
 9399
 9400    private static Dictionary<(Type, string), Func<object, object>> _cachedGetters = new Dictionary<(Type, string), Func
 9401
 9402    internal static bool CheckDraw(DoIfAttributeBase doIf, SerializedObject serializedObject) {
 9403      var compareProperty = serializedObject.FindProperty(doIf.ConditionMember);
 9404
 9405      if (compareProperty != null) {
 9406        return CheckProperty(doIf, compareProperty);
 9407      }
 9408
 9409      return CheckGetter(doIf, serializedObject, 0, string.Empty) == true;
 9410    }
 9411
 9412    internal static bool CheckDraw(DoIfAttributeBase doIf, SerializedProperty property) {
 9413      var compareProperty = property.depth < 0 ? property.FindPropertyRelative(doIf.ConditionMember) : property.FindProp
 9414
 9415      if (compareProperty != null) {
 9416        return CheckProperty(doIf, compareProperty);
 9417      }
 9418
 9419      return CheckGetter(doIf, property.serializedObject, property.depth, property.propertyPath) == true;
 9420    }
 9421
 9422    private static bool CheckProperty(DoIfAttributeBase doIf, SerializedProperty compareProperty) {
 9423      switch (compareProperty.propertyType) {
 9424        case SerializedPropertyType.Boolean:
 9425        case SerializedPropertyType.Integer:
 9426        case SerializedPropertyType.Enum:
 9427        case SerializedPropertyType.Character:
 9428          return CheckCondition(doIf, compareProperty.longValue);
 9429
 9430        case SerializedPropertyType.ObjectReference:
 9431          return CheckCondition(doIf, compareProperty.objectReferenceInstanceIDValue);
 9432
 9433        case SerializedPropertyType.Float:
 9434          return CheckCondition(doIf, compareProperty.doubleValue);
 9435
 9436        default:
 9437          FusionEditorLog.ErrorInspector($"Can't check condition for {compareProperty.propertyPath}: unsupported propert
 9438          return true;
 9439      }
 9440    }
 9441
 9442    private static bool? CheckGetter(DoIfAttributeBase doIf, SerializedObject serializedObject, int depth, string refere
 9443      var objType = serializedObject.targetObject.GetType();
 9444      if (!_cachedGetters.TryGetValue((objType, doIf.ConditionMember), out var getter)) {
 9445        // maybe this is a top-level property then and we can use reflection?
 9446        if (depth != 0) {
 9447          if (doIf.ErrorOnConditionMemberNotFound) {
 9448            FusionEditorLog.ErrorInspector($"Can't check condition for {referencePath}: non-SerializedProperty checks on
 9449          }
 9450        } else {
 9451          try {
 9452            getter = objType.CreateGetter(doIf.ConditionMember, BindingFlags.Public | BindingFlags.NonPublic | BindingFl
 9453          } catch (Exception e) {
 9454            if (doIf.ErrorOnConditionMemberNotFound) {
 9455              FusionEditorLog.ErrorInspector($"Can't check condition for {referencePath}: unable to create getter for {d
 9456            }
 9457          }
 9458        }
 9459
 9460        _cachedGetters.Add((objType, doIf.ConditionMember), getter);
 9461      }
 9462
 9463      if (getter != null) {
 9464        bool? result = null;
 9465        foreach (var target in serializedObject.targetObjects) {
 9466          bool targetResult = CheckCondition(doIf, getter(target));
 9467          if (result.HasValue && result.Value != targetResult) {
 9468            return null;
 9469          } else {
 9470            result = targetResult;
 9471          }
 9472        }
 9473
 9474        return result;
 9475      } else {
 9476        return true;
 9477      }
 9478    }
 9479
 9480    public static bool CheckCondition(DoIfAttributeBase attribute, double value) {
 9481      if (!attribute._isDouble) throw new InvalidOperationException();
 9482
 9483      var doubleValue = attribute._doubleValue;
 9484      switch (attribute.Compare) {
 9485        case CompareOperator.Equal:                  return value == doubleValue;
 9486        case CompareOperator.NotEqual:               return value != doubleValue;
 9487        case CompareOperator.Less:                   return value < doubleValue;
 9488        case CompareOperator.LessOrEqual:            return value <= doubleValue;
 9489        case CompareOperator.GreaterOrEqual:         return value >= doubleValue;
 9490        case CompareOperator.Greater:                return value > doubleValue;
 9491        case CompareOperator.NotZero:                return value != 0;
 9492        case CompareOperator.IsZero:                 return value == 0;
 9493        case CompareOperator.BitwiseAndNotEqualZero: throw new NotSupportedException();
 9494        default:                                     throw new ArgumentOutOfRangeException();
 9495      }
 9496    }
 9497
 9498    public static bool CheckCondition(DoIfAttributeBase attribute, long value) {
 9499      if (attribute._isDouble) throw new InvalidOperationException();
 9500
 9501      var _longValue = attribute._longValue;
 9502      switch (attribute.Compare) {
 9503        case CompareOperator.Equal:                  return value == _longValue;
 9504        case CompareOperator.NotEqual:               return value != _longValue;
 9505        case CompareOperator.Less:                   return value < _longValue;
 9506        case CompareOperator.LessOrEqual:            return value <= _longValue;
 9507        case CompareOperator.GreaterOrEqual:         return value >= _longValue;
 9508        case CompareOperator.Greater:                return value > _longValue;
 9509        case CompareOperator.NotZero:                return value != 0;
 9510        case CompareOperator.IsZero:                 return value == 0;
 9511        case CompareOperator.BitwiseAndNotEqualZero: return (value & _longValue) != 0;
 9512        default:                                     throw new ArgumentOutOfRangeException();
 9513      }
 9514    }
 9515
 9516    public static bool CheckCondition(DoIfAttributeBase attribute, object value) {
 9517      if (attribute._isDouble) {
 9518        double converted = 0.0;
 9519        if (value != null) {
 9520          if (value is UnityEngine.Object o && !o) {
 9521            // treat as 0
 9522          } else if (value.GetType().IsValueType) {
 9523            converted = Convert.ToDouble(value);
 9524          } else {
 9525            converted = 1.0;
 9526          }
 9527        }
 9528
 9529        return CheckCondition(attribute, converted);
 9530      } else {
 9531        long converted = 0;
 9532        if (value != null) {
 9533          if (value is UnityEngine.Object o && !o) {
 9534            // treat as 0
 9535          } else if (value.GetType().IsValueType) {
 9536            converted = Convert.ToInt64(value);
 9537          } else {
 9538            converted = 1;
 9539          }
 9540        }
 9541
 9542        return CheckCondition(attribute, converted);
 9543      }
 9544    }
 9545  }
 9546}
 9547
 9548#endregion
 9549
 9550
 9551#region DrawIfAttributeDrawer.cs
 9552
 9553namespace Fusion.Editor {
 9554  using UnityEditor;
 9555  using UnityEngine;
 9556
 9557  internal partial class DrawIfAttributeDrawer : DoIfAttributeDrawer {
 9558    public DrawIfAttribute Attribute => (DrawIfAttribute)attribute;
 9559
 9560    protected override float GetPropertyHeightInternal(SerializedProperty property, GUIContent label) {
 9561      if (Attribute.Mode == DrawIfMode.ReadOnly || CheckDraw(Attribute, property)) {
 9562        return base.GetPropertyHeightInternal(property, label);
 9563      }
 9564
 9565      return -EditorGUIUtility.standardVerticalSpacing;
 9566    }
 9567
 9568    protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 9569      var readOnly = Attribute.Mode == DrawIfMode.ReadOnly;
 9570      var draw     = CheckDraw(Attribute, property);
 9571
 9572      if (readOnly || draw) {
 9573        EditorGUI.BeginDisabledGroup(!draw);
 9574
 9575        base.OnGUIInternal(position, property, label);
 9576
 9577        EditorGUI.EndDisabledGroup();
 9578      }
 9579    }
 9580  }
 9581
 9582  [CustomPropertyDrawer(typeof(DrawIfAttribute))]
 9583  [RedirectCustomPropertyDrawer(typeof(DrawIfAttribute), typeof(DrawIfAttributeDrawer))]
 9584  partial class PropertyDrawerForArrayWorkaround {
 9585  }
 9586}
 9587
 9588#endregion
 9589
 9590
 9591#region DrawInlineAttributeDrawer.cs
 9592
 9593namespace Fusion.Editor {
 9594  using UnityEditor;
 9595  using UnityEngine;
 9596
 9597  [CustomPropertyDrawer(typeof(DrawInlineAttribute))]
 9598  [FusionPropertyDrawerMeta(HasFoldout = false)]
 9599  internal partial class DrawInlineAttributeDrawer : PropertyDrawer {
 9600    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
 9601      EditorGUI.BeginProperty(position, label, property);
 9602
 9603      foreach (var childProperty in property.GetChildren()) {
 9604        position.height = EditorGUI.GetPropertyHeight(childProperty, true);
 9605        EditorGUI.PropertyField(position, childProperty, true);
 9606        position.y += position.height + EditorGUIUtility.standardVerticalSpacing;
 9607      }
 9608
 9609      EditorGUI.EndProperty();
 9610    }
 9611
 9612    public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
 9613      float height = 0f;
 9614
 9615      foreach (var childProperty in property.GetChildren()) {
 9616        height += EditorGUI.GetPropertyHeight(childProperty, true) + EditorGUIUtility.standardVerticalSpacing;
 9617      }
 9618
 9619      height -= EditorGUIUtility.standardVerticalSpacing;
 9620      return height;
 9621    }
 9622  }
 9623}
 9624
 9625#endregion
 9626
 9627
 9628#region ErrorIfAttributeDrawer.cs
 9629
 9630namespace Fusion.Editor {
 9631  using UnityEditor;
 9632  using UnityEngine;
 9633
 9634  internal partial class ErrorIfAttributeDrawer : MessageIfDrawerBase {
 9635    private new ErrorIfAttribute Attribute => (ErrorIfAttribute)attribute;
 9636
 9637    protected override bool        IsBox          => Attribute.AsBox;
 9638    protected override string      Message        => Attribute.Message;
 9639    protected override MessageType MessageType    => MessageType.Error;
 9640    override protected Color       InlineBoxColor => FusionEditorSkin.ErrorInlineBoxColor;
 9641    protected override Texture     MessageIcon    => FusionEditorSkin.ErrorIcon;
 9642  }
 9643
 9644  [CustomPropertyDrawer(typeof(ErrorIfAttribute))]
 9645  [RedirectCustomPropertyDrawer(typeof(ErrorIfAttribute), typeof(ErrorIfAttributeDrawer))]
 9646  partial class PropertyDrawerForArrayWorkaround {
 9647  }
 9648}
 9649
 9650
 9651#endregion
 9652
 9653
 9654#region ExpandableEnumAttributeDrawer.cs
 9655
 9656namespace Fusion.Editor {
 9657  using System;
 9658  using System.Reflection;
 9659  using UnityEditor;
 9660  using UnityEngine;
 9661
 9662  [CustomPropertyDrawer(typeof(ExpandableEnumAttribute))]
 9663  internal class ExpandableEnumAttributeDrawer : PropertyDrawerWithErrorHandling {
 9664
 9665    private const float ToggleIndent = 5;
 9666
 9667    private readonly GUIContent[]            _gridOptions = new[] { new GUIContent("Nothing"), new GUIContent("Everythin
 9668    private          EnumDrawer              _enumDrawer;
 9669    private readonly LazyGUIStyle            _buttonStyle = LazyGUIStyle.Create(_ => new GUIStyle(EditorStyles.miniButto
 9670
 9671    private new    ExpandableEnumAttribute attribute => (ExpandableEnumAttribute)base.attribute;
 9672
 9673    protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 9674
 9675      bool wasExpanded = attribute.AlwaysExpanded || property.isExpanded;
 9676
 9677      var rowRect = new Rect(position) {
 9678        height = EditorGUIUtility.singleLineHeight,
 9679      };
 9680
 9681      using (new FusionEditorGUI.PropertyScope(position, label, property)) {
 9682        var  valueRect = EditorGUI.PrefixLabel(rowRect, label);
 9683
 9684        bool isEnum        = property.propertyType == SerializedPropertyType.Enum;
 9685        var  maskProperty  = isEnum ? property : property.FindPropertyRelative("Mask").FindPropertyRelative("values");
 9686
 9687        Mask256 rawValue;
 9688        if (isEnum) {
 9689          rawValue = new Mask256(maskProperty.longValue);
 9690
 9691        } else {
 9692          rawValue = new Mask256(
 9693            maskProperty.GetFixedBufferElementAtIndex(0).longValue,
 9694            maskProperty.GetFixedBufferElementAtIndex(1).longValue,
 9695            maskProperty.GetFixedBufferElementAtIndex(2).longValue,
 9696            maskProperty.GetFixedBufferElementAtIndex(3).longValue
 9697            );
 9698        }
 9699        var foldoutRect = new Rect(valueRect) { width = FusionEditorGUI.FoldoutWidth };
 9700        valueRect.xMin += foldoutRect.width;
 9701
 9702        EditorGUI.BeginChangeCheck();
 9703        if (wasExpanded) {
 9704          if (_enumDrawer.IsFlags && attribute.ShowFlagsButtons) {
 9705            int gridValue = -1;
 9706            if (rawValue.IsNothing()) {
 9707              // nothing
 9708              gridValue = 0;
 9709            } else if (Equals(_enumDrawer.BitMask & rawValue, _enumDrawer.BitMask)) {
 9710
 9711              var test = _enumDrawer.BitMask & rawValue;
 9712              if (Equals(test, _enumDrawer.BitMask))
 9713              // everything
 9714              gridValue = 1;
 9715            }
 9716
 9717            // traverse values in reverse; make sure the first alias is used in case there are multiple
 9718            if (isEnum) {
 9719              for (int i = _enumDrawer.Values.Length; i-- > 0;) {
 9720                if (_enumDrawer.Values[i] == 0) {
 9721                  _gridOptions[0].text = _enumDrawer.Names[i];
 9722                } else if ( _enumDrawer.Values[i] == _enumDrawer.BitMask[0]) {
 9723                  // Unity's drawer does not replace "Everything"
 9724                  _gridOptions[1].text = _enumDrawer.Names[i];
 9725                }
 9726              }
 9727            }
 9728
 9729            var gridSelection = GUI.SelectionGrid(valueRect, gridValue, _gridOptions, _gridOptions.Length, _buttonStyle)
 9730            if (gridSelection != gridValue) {
 9731              if (gridSelection == 0) {
 9732                rawValue = default;
 9733              } else if (gridSelection == 1) {
 9734                rawValue = _enumDrawer.BitMask;
 9735              }
 9736            }
 9737          } else {
 9738            // draw a dummy field to consume the prefix
 9739            EditorGUI.LabelField(valueRect, GUIContent.none);
 9740          }
 9741        } else {
 9742          if (isEnum) {
 9743            var enumValue = (Enum)Enum.ToObject(_enumDrawer.EnumType, rawValue[0]);
 9744            if (_enumDrawer.IsFlags) {
 9745              enumValue = EditorGUI.EnumFlagsField(valueRect, enumValue);
 9746            } else {
 9747              enumValue = EditorGUI.EnumPopup(valueRect, enumValue);
 9748            }
 9749
 9750            rawValue[0] = Convert.ToInt64(enumValue);
 9751          } else {
 9752            // Droplist for FieldsMask<T>
 9753            _enumDrawer.Draw(valueRect, maskProperty, fieldInfo.FieldType, false);
 9754          }
 9755        }
 9756
 9757        if (EditorGUI.EndChangeCheck()) {
 9758          if (isEnum) {
 9759            maskProperty.longValue = rawValue[0];
 9760          } else {
 9761            maskProperty.GetFixedBufferElementAtIndex(0).longValue = rawValue[0];
 9762            maskProperty.GetFixedBufferElementAtIndex(1).longValue = rawValue[1];
 9763            maskProperty.GetFixedBufferElementAtIndex(2).longValue = rawValue[2];
 9764            maskProperty.GetFixedBufferElementAtIndex(3).longValue = rawValue[3];
 9765          }
 9766          property.serializedObject.ApplyModifiedProperties();
 9767        }
 9768
 9769        if (!attribute.AlwaysExpanded) {
 9770          using (new FusionEditorGUI.EnabledScope(true)) {
 9771            property.isExpanded = EditorGUI.Toggle(foldoutRect, wasExpanded, EditorStyles.foldout);
 9772          }
 9773        }
 9774
 9775        if (wasExpanded) {
 9776          if (Event.current.type == EventType.Repaint) {
 9777            EditorStyles.helpBox.Draw(new Rect(position) { yMin = rowRect.yMax }, GUIContent.none, false, false, false, 
 9778          }
 9779
 9780          EditorGUI.BeginChangeCheck();
 9781
 9782          rowRect.xMin += ToggleIndent;
 9783
 9784          for (int i = 0; i < _enumDrawer.Values.Length; ++i) {
 9785            if (_enumDrawer.IsFlags && _enumDrawer.Values[i].IsNothing()) {
 9786              continue;
 9787            }
 9788
 9789            rowRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
 9790
 9791            var toggleRect = rowRect;
 9792            var buttonRect = new Rect();
 9793            if (attribute.ShowInlineHelp) {
 9794              // move the button to keep it in the box
 9795              buttonRect      =  FusionEditorGUI.GetInlineHelpButtonRect(rowRect);
 9796              toggleRect.xMin += buttonRect.width + 0;
 9797              buttonRect.x    += buttonRect.width - 3;
 9798            }
 9799
 9800            bool wasSelected = _enumDrawer.IsFlags
 9801              ? Equals(rawValue & _enumDrawer.Values[i], _enumDrawer.Values[i])
 9802              : Equals(rawValue, _enumDrawer.Values[i]);
 9803            if (EditorGUI.ToggleLeft(toggleRect, _enumDrawer.Names[i], wasSelected) != wasSelected) {
 9804              if (_enumDrawer.IsFlags) {
 9805                if (wasSelected) {
 9806                  rawValue &= ~_enumDrawer.Values[i];
 9807                } else {
 9808                  rawValue |= _enumDrawer.Values[i];
 9809                }
 9810              } else if (!wasSelected) {
 9811                rawValue = _enumDrawer.Values[i];
 9812              }
 9813            }
 9814
 9815            if (attribute.ShowInlineHelp) {
 9816              var helpContent = FusionCodeDoc.FindEntry(_enumDrawer.Fields[i], false);
 9817              if (helpContent != null) {
 9818                var helpPath = GetHelpPath(property, _enumDrawer.Fields[i]);
 9819
 9820                var wasHelpExpanded = FusionEditorGUI.IsHelpExpanded(this, helpPath);
 9821                if (wasHelpExpanded) {
 9822                  var helpSize = FusionEditorGUI.GetInlineBoxSize(helpContent);
 9823                  var helpRect = rowRect;
 9824                  helpRect.y      += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
 9825                  helpRect.height =  helpSize.y;
 9826
 9827                  rowRect.y += helpSize.y;
 9828
 9829                  FusionEditorGUI.DrawInlineBoxUnderProperty(helpContent, helpRect, FusionEditorSkin.HelpInlineBoxColor,
 9830                }
 9831
 9832                buttonRect.x += buttonRect.width;
 9833                if (FusionEditorGUI.DrawInlineHelpButton(buttonRect, wasHelpExpanded, doButton: true, doIcon: true)) {
 9834                  FusionEditorGUI.SetHelpExpanded(this, helpPath, !wasHelpExpanded);
 9835                }
 9836              }
 9837            }
 9838          }
 9839
 9840          if (EditorGUI.EndChangeCheck()) {
 9841            if (isEnum) {
 9842              maskProperty.longValue = rawValue[0];
 9843            } else {
 9844              maskProperty.GetFixedBufferElementAtIndex(0).longValue = rawValue[0];
 9845              maskProperty.GetFixedBufferElementAtIndex(1).longValue = rawValue[1];
 9846              maskProperty.GetFixedBufferElementAtIndex(2).longValue = rawValue[2];
 9847              maskProperty.GetFixedBufferElementAtIndex(3).longValue = rawValue[3];
 9848            }
 9849            property.serializedObject.ApplyModifiedProperties();
 9850          }
 9851        }
 9852      }
 9853    }
 9854
 9855
 9856    public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
 9857
 9858      var enumType = property.propertyType == SerializedPropertyType.Enum ? fieldInfo.FieldType.GetUnityLeafType() : fie
 9859      _enumDrawer.EnsureInitialized(enumType, attribute.ShowInlineHelp);
 9860
 9861      int rowCount = 0;
 9862
 9863      float height;
 9864
 9865      var forceExpand = attribute.AlwaysExpanded;
 9866      var showHelp    = attribute.ShowInlineHelp;
 9867
 9868      if (forceExpand || property.isExpanded) {
 9869        if (_enumDrawer.IsFlags) {
 9870          foreach (var value in _enumDrawer.Values) {
 9871            if (value.IsNothing()) {
 9872              continue;
 9873            }
 9874
 9875            ++rowCount;
 9876          }
 9877        } else {
 9878          rowCount = _enumDrawer.Values.Length;
 9879        }
 9880
 9881        height = (rowCount + 1) * (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing);
 9882
 9883        if (showHelp) {
 9884          foreach (var field in _enumDrawer.Fields) {
 9885            if (FusionEditorGUI.IsHelpExpanded(this, GetHelpPath(property, field))) {
 9886              var helpContent = FusionCodeDoc.FindEntry(field, false);
 9887              if (helpContent != null) {
 9888                height += FusionEditorGUI.GetInlineBoxSize(helpContent).y;
 9889              }
 9890            }
 9891          }
 9892        }
 9893
 9894      } else {
 9895        height = EditorGUIUtility.singleLineHeight;
 9896      }
 9897
 9898      return height;
 9899    }
 9900
 9901    private static string GetHelpPath(SerializedProperty property, FieldInfo field) {
 9902      return property.propertyPath + "/" + field.Name;
 9903    }
 9904  }
 9905}
 9906
 9907#endregion
 9908
 9909
 9910#region FieldEditorButtonAttributeDrawer.cs
 9911
 9912namespace Fusion.Editor {
 9913  using System;
 9914  using System.Reflection;
 9915  using UnityEditor;
 9916  using UnityEngine;
 9917  using Object = UnityEngine.Object;
 9918
 9919  internal partial class FieldEditorButtonAttributeDrawer : DecoratingPropertyAttributeDrawer {
 9920    protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 9921
 9922      var propertyPosition = position;
 9923      propertyPosition.height -= EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
 9924
 9925      base.OnGUIInternal(propertyPosition, property, label);
 9926
 9927      var buttonPosition = position;
 9928      buttonPosition.yMin = position.yMax - EditorGUIUtility.singleLineHeight;
 9929
 9930      var attribute        = (FieldEditorButtonAttribute)this.attribute;
 9931      var targetObjects    = property.serializedObject.targetObjects;
 9932      var targetObjectType = property.serializedObject.targetObject.GetType();
 9933
 9934      if (DrawButton(buttonPosition, attribute, targetObjectType, targetObjects)) {
 9935        property.serializedObject.Update();
 9936        property.serializedObject.ApplyModifiedProperties();
 9937      }
 9938    }
 9939
 9940    private static bool DrawButton(Rect buttonPosition, FieldEditorButtonAttribute attribute, Type targetObjectType, Obj
 9941      using (new EditorGUI.DisabledGroupScope(!attribute.AllowMultipleTargets && targetObjects.Length > 1)) {
 9942        if (GUI.Button(buttonPosition, attribute.Label, EditorStyles.miniButton)) {
 9943          var targetMethod = targetObjectType.GetMethod(attribute.TargetMethod, BindingFlags.Public | BindingFlags.NonPu
 9944          if (targetMethod == null) {
 9945            FusionEditorLog.ErrorInspector($"Unable to find method {attribute.TargetMethod} on type {targetObjectType}")
 9946          } else {
 9947            if (targetMethod.IsStatic) {
 9948              targetMethod.Invoke(null, null);
 9949            } else {
 9950              foreach (var targetObject in targetObjects) {
 9951                targetMethod.Invoke(targetObject, null);
 9952              }
 9953            }
 9954
 9955            return true;
 9956          }
 9957        }
 9958
 9959        return false;
 9960      }
 9961    }
 9962
 9963    protected override float GetPropertyHeightInternal(SerializedProperty property, GUIContent label) {
 9964      return base.GetPropertyHeightInternal(property, label) + EditorGUIUtility.standardVerticalSpacing + EditorGUIUtili
 9965    }
 9966  }
 9967
 9968  [CustomPropertyDrawer(typeof(FieldEditorButtonAttribute))]
 9969  [RedirectCustomPropertyDrawer(typeof(FieldEditorButtonAttribute), typeof(FieldEditorButtonAttributeDrawer))]
 9970  partial class PropertyDrawerForArrayWorkaround {
 9971  }
 9972}
 9973
 9974#endregion
 9975
 9976
 9977#region HideArrayElementLabelAttributeDrawer.cs
 9978
 9979namespace Fusion.Editor {
 9980  using UnityEditor;
 9981  using UnityEngine;
 9982
 9983  [CustomPropertyDrawer(typeof(HideArrayElementLabelAttribute))]
 9984  partial class HideArrayElementLabelAttributeDrawer : DecoratingPropertyAttributeDrawer {
 9985    protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 9986      if (property.IsArrayElement()) {
 9987        label = GUIContent.none;
 9988      }
 9989      base.OnGUIInternal(position, property, label);
 9990    }
 9991  }
 9992}
 9993
 9994#endregion
 9995
 9996
 9997#region InlineHelpAttributeDrawer.cs
 9998
 9999namespace Fusion.Editor {
 10000  using System.Reflection;
 10001  using UnityEditor;
 10002  using UnityEngine;
 10003
 10004  //[CustomPropertyDrawer(typeof(InlineHelpAttribute))]
 10005  internal partial class InlineHelpAttributeDrawer : DecoratingPropertyAttributeDrawer, INonApplicableOnArrayElements {
 10006
 10007    private bool       _initialized;
 10008    private GUIContent _helpContent;
 10009    private GUIContent _labelContent;
 10010
 10011    protected new InlineHelpAttribute attribute => (InlineHelpAttribute)base.attribute;
 10012
 10013
 10014    protected override float GetPropertyHeightInternal(SerializedProperty property, GUIContent label) {
 10015
 10016      var height = base.GetPropertyHeightInternal(property, label);
 10017      if (height <= 0) {
 10018        return height;
 10019      }
 10020
 10021      if (FusionEditorGUI.IsHelpExpanded(this, property.propertyPath)) {
 10022        var helpContent = GetHelpContent(property);
 10023        if (helpContent != null) {
 10024          height += FusionEditorGUI.GetInlineBoxSize(helpContent).y;
 10025        }
 10026      }
 10027
 10028      return height;
 10029    }
 10030
 10031    protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 10032
 10033      var helpContent = GetHelpContent(property);
 10034
 10035      if (position.height <= 0 || helpContent == null) {
 10036        // ignore
 10037        base.OnGUIInternal(position, property, label);
 10038        return;
 10039      }
 10040
 10041      var nextDrawer = GetNextDrawer(property);
 10042      var hasFoldout = HasFoldout(nextDrawer, property);
 10043
 10044      using (new FusionEditorGUI.GUIContentScope(label)) {
 10045        var (wasExpanded, buttonRect) = DrawInlineHelpBeforeProperty(label, helpContent, position, property.propertyPath
 10046
 10047        var propertyRect = position;
 10048        if (wasExpanded) {
 10049          propertyRect.height -= FusionEditorGUI.GetInlineBoxSize(helpContent).y;
 10050        }
 10051        base.OnGUIInternal(propertyRect, property, label);
 10052
 10053        DrawInlineHelpAfterProperty(buttonRect, wasExpanded, helpContent, position);
 10054      }
 10055    }
 10056
 10057    private GUIContent GetHelpContent(SerializedProperty property) {
 10058      if (_initialized) {
 10059        return _helpContent;
 10060      }
 10061
 10062      _initialized = true;
 10063
 10064      if (property.IsArrayElement()) {
 10065        return null;
 10066      }
 10067
 10068      if (fieldInfo == null) {
 10069        return null;
 10070      }
 10071
 10072      _helpContent = FusionCodeDoc.FindEntry(fieldInfo, attribute.ShowTypeHelp);
 10073      return _helpContent;
 10074    }
 10075
 10076    private bool HasFoldout(PropertyDrawer nextDrawer, SerializedProperty property) {
 10077      var drawerMeta = nextDrawer?.GetType().GetCustomAttribute<FusionPropertyDrawerMetaAttribute>();
 10078      if (drawerMeta != null) {
 10079        return drawerMeta.HasFoldout;
 10080      }
 10081
 10082      if (property.IsArrayProperty()) {
 10083        return true;
 10084      }
 10085
 10086      if (property.propertyType == SerializedPropertyType.Generic) {
 10087        return true;
 10088      }
 10089
 10090      return false;
 10091    }
 10092
 10093    public static (bool expanded, Rect buttonRect) DrawInlineHelpBeforeProperty(GUIContent label, GUIContent helpContent
 10094
 10095      if (label != null) {
 10096        if (!string.IsNullOrEmpty(label.tooltip)) {
 10097          label.tooltip += "\n\n";
 10098        }
 10099        label.tooltip += helpContent.tooltip;
 10100      }
 10101
 10102      if (propertyRect.width > 1 && propertyRect.height > 1) {
 10103        var buttonRect = FusionEditorGUI.GetInlineHelpButtonRect(propertyRect, hasFoldout);
 10104
 10105        if (depth == 0 && hasFoldout) {
 10106          buttonRect.x = 16;
 10107          if (label != null) {
 10108            label.text = "    " + label.text;
 10109          }
 10110        }
 10111
 10112        var wasExpanded = FusionEditorGUI.IsHelpExpanded(context, propertyPath);
 10113
 10114        if (FusionEditorGUI.DrawInlineHelpButton(buttonRect, wasExpanded, doButton: true, doIcon: false)) {
 10115          FusionEditorGUI.SetHelpExpanded(context, propertyPath, !wasExpanded);
 10116        }
 10117
 10118        return (wasExpanded, buttonRect);
 10119      }
 10120
 10121      return default;
 10122    }
 10123
 10124    public static void DrawInlineHelpAfterProperty(Rect buttonRect, bool wasExpanded, GUIContent helpContent, Rect prope
 10125
 10126      if (buttonRect.width <= 0 && buttonRect.height <= 0) {
 10127        return;
 10128      }
 10129
 10130      using (new FusionEditorGUI.EnabledScope(true)) {
 10131        FusionEditorGUI.DrawInlineHelpButton(buttonRect, wasExpanded, doButton: false, doIcon: true);
 10132      }
 10133
 10134      if (!wasExpanded) {
 10135        return;
 10136      }
 10137
 10138      FusionEditorGUI.DrawInlineBoxUnderProperty(helpContent, propertyRect, FusionEditorSkin.HelpInlineBoxColor, true);
 10139    }
 10140  }
 10141
 10142
 10143  [CustomPropertyDrawer(typeof(InlineHelpAttribute))]
 10144  [RedirectCustomPropertyDrawer(typeof(InlineHelpAttribute), typeof(InlineHelpAttributeDrawer))]
 10145  partial class PropertyDrawerForArrayWorkaround {
 10146  }
 10147}
 10148
 10149#endregion
 10150
 10151
 10152#region INonApplicableOnArrayElements.cs
 10153
 10154namespace Fusion.Editor {
 10155  interface INonApplicableOnArrayElements {
 10156  }
 10157}
 10158
 10159#endregion
 10160
 10161
 10162#region LayerAttributeDrawer.cs
 10163
 10164namespace Fusion.Editor {
 10165  using UnityEditor;
 10166  using UnityEngine;
 10167
 10168  [CustomPropertyDrawer(typeof(LayerAttribute))]
 10169  internal class LayerAttributeDrawer : PropertyDrawer {
 10170    public override void OnGUI(Rect p, SerializedProperty prop, GUIContent label) {
 10171      EditorGUI.BeginChangeCheck();
 10172
 10173      int value;
 10174
 10175      using (new FusionEditorGUI.PropertyScope(p, label, prop))
 10176      using (new FusionEditorGUI.ShowMixedValueScope(prop.hasMultipleDifferentValues)) {
 10177        value = EditorGUI.LayerField(p, label, prop.intValue);
 10178      }
 10179
 10180      if (EditorGUI.EndChangeCheck()) {
 10181        prop.intValue = value;
 10182        prop.serializedObject.ApplyModifiedProperties();
 10183      }
 10184    }
 10185  }
 10186}
 10187
 10188#endregion
 10189
 10190
 10191#region LayerMatrixAttributeDrawer.cs
 10192
 10193namespace Fusion.Editor {
 10194  using UnityEditor;
 10195  using UnityEngine;
 10196
 10197  internal partial class LayerMatrixAttributeDrawer : PropertyDrawerWithErrorHandling, INonApplicableOnArrayElements {
 10198
 10199    protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 10200      using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out var valueRect)) {
 10201        if (GUI.Button(valueRect, "Edit", EditorStyles.miniButton)) {
 10202          PopupWindow.Show(valueRect, new LayerMatrixPopup(label?.text ?? property.displayName,
 10203            (layerA, layerB) => {
 10204              if (layerA >= property.arraySize) {
 10205                return false;
 10206              }
 10207
 10208              return (property.GetArrayElementAtIndex(layerA).intValue & (1 << layerB)) != 0;
 10209            },
 10210            (layerA, layerB, val) => {
 10211              if (Mathf.Max(layerA, layerB) >= property.arraySize) {
 10212                property.arraySize = Mathf.Max(layerA, layerB) + 1;
 10213              }
 10214              if (val) {
 10215                property.GetArrayElementAtIndex(layerA).intValue |= (1 << layerB);
 10216                property.GetArrayElementAtIndex(layerB).intValue |= (1 << layerA);
 10217              } else {
 10218                property.GetArrayElementAtIndex(layerA).intValue &= ~(1 << layerB);
 10219                property.GetArrayElementAtIndex(layerB).intValue &= ~(1 << layerA);
 10220              }
 10221              property.serializedObject.ApplyModifiedProperties();
 10222            }));
 10223        }
 10224      }
 10225    }
 10226
 10227    public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
 10228      return EditorGUIUtility.singleLineHeight;
 10229    }
 10230
 10231    class LayerMatrixPopup : PopupWindowContent {
 10232      private const int checkboxSize = 16;
 10233      private const int margin       = 30;
 10234      private const int MaxLayers    = 32;
 10235
 10236      private readonly GUIContent _label;
 10237      private readonly int _numLayers;
 10238      private readonly float _labelWidth;
 10239
 10240      private readonly UnityInternal.LayerMatrixGUI.GetValueFunc _getter;
 10241      private readonly UnityInternal.LayerMatrixGUI.SetValueFunc _setter;
 10242
 10243      public LayerMatrixPopup(string label, UnityInternal.LayerMatrixGUI.GetValueFunc getter, UnityInternal.LayerMatrixG
 10244        _label      = new GUIContent(label);
 10245        _getter     = getter;
 10246        _setter     = setter;
 10247        _labelWidth = 110;
 10248        _numLayers  = 0;
 10249        for (int i = 0; i < MaxLayers; i++) {
 10250          string layerName = LayerMask.LayerToName(i);
 10251          if (string.IsNullOrEmpty(layerName)) {
 10252            continue;
 10253          }
 10254
 10255          _numLayers++;
 10256          _labelWidth = Mathf.Max(_labelWidth, GUI.skin.label.CalcSize(new GUIContent(layerName)).x);
 10257        }
 10258      }
 10259
 10260      public override void OnGUI(Rect rect) {
 10261        GUILayout.BeginArea(rect);
 10262
 10263        UnityInternal.LayerMatrixGUI.Draw(_label, _getter, _setter);
 10264
 10265        GUILayout.EndArea();
 10266      }
 10267
 10268      public override Vector2 GetWindowSize() {
 10269        int   matrixWidth = checkboxSize * _numLayers;
 10270        float width       = matrixWidth + _labelWidth + margin * 2;
 10271        float height      = matrixWidth + _labelWidth + 15 + FusionEditorGUI.GetLinesHeight(3);
 10272        return new Vector2(Mathf.Max(width, 350), height);
 10273      }
 10274    }
 10275  }
 10276
 10277  [CustomPropertyDrawer(typeof(LayerMatrixAttribute))]
 10278  [RedirectCustomPropertyDrawer(typeof(LayerMatrixAttribute), typeof(LayerMatrixAttributeDrawer))]
 10279  partial class PropertyDrawerForArrayWorkaround {
 10280  }
 10281}
 10282
 10283#endregion
 10284
 10285
 10286#region MaxStringByteCountAttributeDrawer.cs
 10287
 10288namespace Fusion.Editor {
 10289  using UnityEditor;
 10290  using UnityEngine;
 10291
 10292  [CustomPropertyDrawer(typeof(MaxStringByteCountAttribute))]
 10293  internal class MaxStringByteCountAttributeDrawer : PropertyDrawerWithErrorHandling {
 10294
 10295    protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 10296      var attribute = (MaxStringByteCountAttribute)this.attribute;
 10297
 10298      var encoding  = System.Text.Encoding.GetEncoding(attribute.Encoding);
 10299      var byteCount = encoding.GetByteCount(property.stringValue);
 10300
 10301      using (new FusionEditorGUI.PropertyScope(position, label, property)) {
 10302        FusionEditorGUI.ForwardPropertyField(position, property, label, true);
 10303      }
 10304
 10305      FusionEditorGUI.Overlay(position, $"({byteCount} B)");
 10306      if (byteCount > attribute.ByteCount) {
 10307        FusionEditorGUI.Decorate(position, $"{attribute.Encoding} string max size ({attribute.ByteCount} B) exceeded: {b
 10308      }
 10309    }
 10310  }
 10311}
 10312
 10313#endregion
 10314
 10315
 10316#region MessageIfDrawerBase.cs
 10317
 10318namespace Fusion.Editor {
 10319  using UnityEditor;
 10320  using UnityEngine;
 10321
 10322  internal abstract class MessageIfDrawerBase : DoIfAttributeDrawer {
 10323    protected abstract bool        IsBox          { get; }
 10324    protected abstract string      Message        { get; }
 10325    protected abstract MessageType MessageType    { get; }
 10326    protected abstract Color       InlineBoxColor { get; }
 10327    protected abstract Texture     MessageIcon    { get; }
 10328
 10329    public DoIfAttributeBase Attribute => (DoIfAttributeBase)attribute;
 10330
 10331    private GUIContent _messageContent;
 10332    private GUIContent MessageContent {
 10333      get {
 10334        if (_messageContent == null) {
 10335          _messageContent = new GUIContent(Message, MessageIcon, Message);
 10336        }
 10337        return _messageContent;
 10338      }
 10339    }
 10340
 10341    protected override float GetPropertyHeightInternal(SerializedProperty property, GUIContent label) {
 10342      var height = base.GetPropertyHeightInternal(property, label);
 10343
 10344      if (IsBox) {
 10345        if (CheckDraw(Attribute, property)) {
 10346          float extra = CalcBoxHeight();
 10347          height += extra;
 10348        }
 10349      }
 10350
 10351      return height;
 10352    }
 10353
 10354    protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 10355
 10356      if (!CheckDraw(Attribute, property)) {
 10357        base.OnGUIInternal(position, property, label);
 10358      } else {
 10359        if (!IsBox) {
 10360
 10361          var decorateRect = position;
 10362          decorateRect.height =  EditorGUIUtility.singleLineHeight;
 10363          decorateRect.xMin   += EditorGUIUtility.labelWidth;
 10364
 10365          // TODO: should the border be resized for arrays?
 10366          // if (property.IsArrayProperty()) {
 10367          //   decorateRect.xMin = decorateRect.xMax - 48f;
 10368          // }
 10369
 10370          FusionEditorGUI.AppendTooltip(MessageContent.text, ref label);
 10371
 10372          base.OnGUIInternal(position, property, label);
 10373
 10374          FusionEditorGUI.Decorate(decorateRect, MessageContent.text, MessageType);
 10375        } else {
 10376
 10377          position = FusionEditorGUI.DrawInlineBoxUnderProperty(MessageContent, position, InlineBoxColor);
 10378          base.OnGUIInternal(position, property, label);
 10379
 10380          //position.y      += position.height;
 10381          //position.height =  extra;
 10382          //EditorGUI.HelpBox(position, MessageContent.text, MessageType);
 10383
 10384        }
 10385      }
 10386    }
 10387
 10388    private float CalcBoxHeight() {
 10389      // const float SCROLL_WIDTH     = 16f;
 10390      // const float LEFT_HELP_INDENT = 8f;
 10391      //
 10392      // var width = UnityInternal.EditorGUIUtility.contextWidth - /*InlineHelpStyle.MarginOuter -*/ SCROLL_WIDTH - LEFT
 10393      // return EditorStyles.helpBox.CalcHeight(MessageContent, width);
 10394
 10395      return FusionEditorGUI.GetInlineBoxSize(MessageContent).y;
 10396    }
 10397  }
 10398}
 10399
 10400#endregion
 10401
 10402
 10403#region PropertyDrawerForArrayWorkaround.cs
 10404
 10405//#define FUSION_EDITOR_TRACE
 10406namespace Fusion.Editor {
 10407  using System;
 10408  using System.Collections.Generic;
 10409  using System.Linq;
 10410  using System.Reflection;
 10411  using UnityEditor;
 10412
 10413  internal partial class PropertyDrawerForArrayWorkaround : DecoratorDrawer {
 10414    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
 10415    internal class RedirectCustomPropertyDrawerAttribute : Attribute {
 10416      public RedirectCustomPropertyDrawerAttribute(Type attributeType, Type drawerType) {
 10417        AttributeType = attributeType;
 10418        DrawerType    = drawerType;
 10419      }
 10420
 10421      public Type AttributeType { get; }
 10422      public Type DrawerType    { get; }
 10423    }
 10424
 10425
 10426    private static Dictionary<Type, Type> _attributeToDrawer = typeof(PropertyDrawerForArrayWorkaround)
 10427     .GetCustomAttributes<RedirectCustomPropertyDrawerAttribute>()
 10428     .ToDictionary(x => x.AttributeType, x => x.DrawerType);
 10429
 10430    private UnityInternal.PropertyHandler _handler;
 10431    private PropertyDrawer                _drawer;
 10432    private bool                          _initialized;
 10433
 10434    public PropertyDrawerForArrayWorkaround() {
 10435      _handler = UnityInternal.ScriptAttributeUtility.nextHandler;
 10436    }
 10437
 10438    public override float GetHeight() {
 10439      if (!_initialized) {
 10440        _initialized = true;
 10441
 10442        if (!_attributeToDrawer.TryGetValue(attribute.GetType(), out var drawerType)) {
 10443          FusionEditorLog.ErrorInspector($"No drawer for {attribute.GetType()}");
 10444        } else if (_handler.decoratorDrawers?.Contains(this) != true) {
 10445          FusionEditorLog.Warn($"Unable to forward to {drawerType}.");
 10446        } else {
 10447          var drawer = (PropertyDrawer)Activator.CreateInstance(drawerType);
 10448
 10449          UnityInternal.PropertyDrawer.SetAttribute(drawer, attribute);
 10450
 10451          // if (_handler.decoratorDrawers.Contains(this)) {
 10452          // }
 10453
 10454          if (_handler.m_PropertyDrawers == null) {
 10455            _handler.m_PropertyDrawers = new List<PropertyDrawer>();
 10456          }
 10457
 10458          var insertPosition = _handler.m_PropertyDrawers.TakeWhile(x => x.attribute != null && x.attribute.order < attr
 10459           .Count();
 10460
 10461          FusionEditorLog.Trace($"Inserting {drawerType} at {insertPosition}");
 10462          _handler.m_PropertyDrawers.Insert(insertPosition, drawer);
 10463        }
 10464      }
 10465
 10466      return 0;
 10467    }
 10468
 10469    public static Type GetDrawerType(Type attributeDrawerType) {
 10470      return _attributeToDrawer[attributeDrawerType];
 10471    }
 10472  }
 10473
 10474  // [CustomPropertyDrawer(typeof(Attrib))]
 10475  // public class DummyDrawer : ForwardingPropertyDrawer {
 10476  //   public class Attrib : PropertyAttribute {
 10477  //   }
 10478  //
 10479  //   public DummyDrawer() {
 10480  //     //ReadOnlyAttribute
 10481  //   }
 10482  //
 10483  //   protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 10484  //     base.OnGUIInternal(position, property, label);
 10485  //   }
 10486  //
 10487  //   protected override float GetPropertyHeightInternal(SerializedProperty property, GUIContent label) {
 10488  //     return base.GetPropertyHeightInternal(property, label);
 10489  //   }
 10490  // }
 10491  //
 10492  // [CustomPropertyDrawer(typeof(Attrib))]
 10493  // public class FooPropertyDrawer : PropertyDrawer {
 10494  //   public class Attrib : PropertyAttribute {
 10495  //     public PropertyAttribute OtherAttribute;
 10496  //     public Type              OtherDrawerType;
 10497  //   }
 10498  //
 10499  //   private PropertyDrawer _otherDrawer;
 10500  //
 10501  //   private void EnsureOtherDrawer(SerializedProperty property) {
 10502  //     if (_otherDrawer == null) {
 10503  //       var attrib = (Attrib)attribute;
 10504  //       _otherDrawer = (PropertyDrawer)Activator.CreateInstance(attrib.OtherDrawerType);
 10505  //       UnityInternal.PropertyDrawer.SetAttribute(_otherDrawer, attrib.OtherAttribute);
 10506  //       UnityInternal.PropertyDrawer.SetFieldInfo(_otherDrawer, fieldInfo);
 10507  //     }
 10508  //   }
 10509  //
 10510  //   public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
 10511  //     EnsureOtherDrawer(property);
 10512  //     return _otherDrawer.GetPropertyHeight(property, label);
 10513  //   }
 10514  //
 10515  //   public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
 10516  //     EnsureOtherDrawer(property);
 10517  //     _otherDrawer.OnGUI(position, property, label);
 10518  //   }
 10519  // }
 10520
 10521
 10522}
 10523
 10524#endregion
 10525
 10526
 10527#region PropertyDrawerWithErrorHandling.cs
 10528
 10529namespace Fusion.Editor {
 10530  using System;
 10531  using System.Collections.Generic;
 10532  using UnityEditor;
 10533  using UnityEngine;
 10534
 10535  internal abstract class PropertyDrawerWithErrorHandling : PropertyDrawer {
 10536    private SerializedProperty _currentProperty;
 10537
 10538    private readonly Dictionary<string, Entry> _errors = new();
 10539    private          bool                      _hadError;
 10540    private          string                    _info;
 10541
 10542    public sealed override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
 10543      FusionEditorLog.Assert(_currentProperty == null);
 10544
 10545      var decoration = GetDecoration(property);
 10546
 10547      if (decoration != null) {
 10548        DrawDecoration(position, decoration.Value, label != GUIContent.none, true, false);
 10549      }
 10550
 10551
 10552      _currentProperty = property;
 10553      _hadError        = false;
 10554      _info            = null;
 10555
 10556      EditorGUI.BeginChangeCheck();
 10557
 10558      try {
 10559        OnGUIInternal(position, property, label);
 10560      } catch (ExitGUIException) {
 10561        // pass through
 10562      } catch (Exception ex) {
 10563        SetError(ex.ToString());
 10564      } finally {
 10565        // if there was a change but no error clear
 10566        if (EditorGUI.EndChangeCheck() && !_hadError) {
 10567          ClearError();
 10568        }
 10569
 10570        _currentProperty = null;
 10571      }
 10572
 10573      if (decoration != null) {
 10574        DrawDecoration(position, decoration.Value, label != GUIContent.none, false, true);
 10575      }
 10576    }
 10577
 10578    private void DrawDecoration(Rect position, (string, MessageType, bool) decoration, bool hasLabel, bool drawButton = 
 10579      var iconPosition = position;
 10580      iconPosition.height =  EditorGUIUtility.singleLineHeight;
 10581      FusionEditorGUI.Decorate(iconPosition, decoration.Item1, decoration.Item2, hasLabel, drawButton: drawButton, drawB
 10582    }
 10583
 10584    private (string, MessageType, bool)? GetDecoration(SerializedProperty property) {
 10585      if (_errors.TryGetValue(property.propertyPath, out var error)) {
 10586        return (error.message, error.type, true);
 10587      }
 10588
 10589      if (_info != null) {
 10590        return (_info, MessageType.Info, false);
 10591      }
 10592
 10593      return null;
 10594    }
 10595
 10596    protected abstract void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label);
 10597
 10598    protected void ClearError() {
 10599      ClearError(_currentProperty);
 10600    }
 10601
 10602    protected void ClearError(SerializedProperty property) {
 10603      _hadError = false;
 10604      _errors.Remove(property.propertyPath);
 10605    }
 10606
 10607    protected void ClearErrorIfLostFocus() {
 10608      if (GUIUtility.keyboardControl != UnityInternal.EditorGUIUtility.LastControlID) {
 10609        ClearError();
 10610      }
 10611    }
 10612
 10613    protected void SetError(string error) {
 10614      _hadError = true;
 10615      _errors[_currentProperty.propertyPath] = new Entry {
 10616        message = error,
 10617        type    = MessageType.Error
 10618      };
 10619    }
 10620
 10621    protected void SetError(Exception error) {
 10622      SetError(error.ToString());
 10623    }
 10624
 10625    protected void SetWarning(string warning) {
 10626      if (_errors.TryGetValue(_currentProperty.propertyPath, out var entry) && entry.type == MessageType.Error) {
 10627        return;
 10628      }
 10629
 10630      _errors[_currentProperty.propertyPath] = new Entry {
 10631        message = warning,
 10632        type    = MessageType.Warning
 10633      };
 10634    }
 10635
 10636    protected void SetInfo(string message) {
 10637      if (_errors.TryGetValue(_currentProperty.propertyPath, out var entry) && entry.type == MessageType.Error || entry.
 10638        return;
 10639      }
 10640
 10641      _errors[_currentProperty.propertyPath] = new Entry {
 10642        message = message,
 10643        type    = MessageType.Info
 10644      };
 10645    }
 10646
 10647    private struct Entry {
 10648      public string      message;
 10649      public MessageType type;
 10650    }
 10651  }
 10652}
 10653
 10654#endregion
 10655
 10656
 10657#region RangeExAttributeDrawer.cs
 10658
 10659namespace Fusion.Editor {
 10660  using UnityEditor;
 10661  using UnityEngine;
 10662
 10663  [CustomPropertyDrawer(typeof(RangeExAttribute))]
 10664  internal partial class RangeExAttributeDrawer : PropertyDrawerWithErrorHandling {
 10665
 10666    const float FieldWidth     = 100.0f;
 10667    const float Spacing        = 5.0f;
 10668    const float SliderOffset   = 2.0f;
 10669    const float MinSliderWidth = 40.0f;
 10670
 10671    partial void GetFloatValue(SerializedProperty property, ref float? floatValue);
 10672    partial void GetIntValue(SerializedProperty property, ref int? intValue);
 10673    partial void ApplyFloatValue(SerializedProperty property, float floatValue);
 10674    partial void ApplyIntValue(SerializedProperty property, int intValue);
 10675    partial void DrawFloatValue(SerializedProperty property, Rect position, GUIContent label, ref float floatValue);
 10676    partial void DrawIntValue(SerializedProperty property, Rect position, GUIContent label, ref int intValue);
 10677
 10678
 10679    protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 10680      var attrib = (RangeExAttribute)this.attribute;
 10681      var min    = attrib.Min;
 10682      var max    = attrib.Max;
 10683
 10684      int?   intValue   = null;
 10685      float? floatValue = null;
 10686
 10687      if (property.propertyType == SerializedPropertyType.Float) {
 10688        floatValue = property.floatValue;
 10689      } else if (property.propertyType == SerializedPropertyType.Integer) {
 10690        intValue = property.intValue;
 10691      } else {
 10692        GetFloatValue(property, ref floatValue);
 10693        if (!floatValue.HasValue) {
 10694          GetIntValue(property, ref intValue);
 10695          if (!intValue.HasValue) {
 10696            EditorGUI.LabelField(position, label.text, "Use RangeEx with float or int.");
 10697            return;
 10698          }
 10699        }
 10700      }
 10701
 10702      Debug.Assert(floatValue.HasValue || intValue.HasValue);
 10703
 10704      EditorGUI.BeginChangeCheck();
 10705
 10706      using (new FusionEditorGUI.PropertyScope(position, label, property)) {
 10707        if (attrib.UseSlider) {
 10708
 10709          // slider offset is applied to look like the built-in RangeDrawer
 10710          var sliderRect = new Rect(position) {
 10711            xMin = position.xMin + EditorGUIUtility.labelWidth + SliderOffset,
 10712            xMax = position.xMax - FieldWidth - Spacing
 10713          };
 10714
 10715          using (new FusionEditorGUI.LabelWidthScope(position.width - FieldWidth)) {
 10716            if (floatValue.HasValue) {
 10717              if (sliderRect.width > MinSliderWidth) {
 10718                using (new EditorGUI.IndentLevelScope(-EditorGUI.indentLevel)) {
 10719                  floatValue = GUI.HorizontalSlider(sliderRect, floatValue.Value, (float)min, (float)max);
 10720                }
 10721              }
 10722
 10723              floatValue = DrawValue(property, position, label, floatValue.Value);
 10724            } else {
 10725              if (sliderRect.width > MinSliderWidth) {
 10726                using (new EditorGUI.IndentLevelScope(-EditorGUI.indentLevel)) {
 10727                  intValue = Mathf.RoundToInt(GUI.HorizontalSlider(sliderRect, intValue.Value, (float)min, (float)max));
 10728                }
 10729              }
 10730
 10731              intValue = DrawValue(property, position, label, intValue.Value);
 10732            }
 10733          }
 10734        } else {
 10735          if (floatValue.HasValue) {
 10736            floatValue = DrawValue(property, position, label, floatValue.Value);
 10737          } else {
 10738            intValue = DrawValue(property, position, label, intValue.Value);
 10739          }
 10740        }
 10741      }
 10742
 10743      if (EditorGUI.EndChangeCheck()) {
 10744        if (floatValue.HasValue) {
 10745          floatValue = Clamp(floatValue.Value, attrib);
 10746        } else {
 10747          Debug.Assert(floatValue != null);
 10748          intValue = Clamp(intValue.Value, attrib);
 10749        }
 10750
 10751        if (property.propertyType == SerializedPropertyType.Float) {
 10752          Debug.Assert(floatValue != null);
 10753          property.floatValue = floatValue.Value;
 10754        } else if (property.propertyType == SerializedPropertyType.Integer) {
 10755          Debug.Assert(intValue != null);
 10756          property.intValue = intValue.Value;
 10757        } else if (floatValue.HasValue) {
 10758          ApplyFloatValue(property, floatValue.Value);
 10759        } else {
 10760          ApplyIntValue(property, intValue.Value);
 10761        }
 10762
 10763        property.serializedObject.ApplyModifiedProperties();
 10764      }
 10765    }
 10766
 10767    private float Clamp(float value, RangeExAttribute attrib) {
 10768      return Mathf.Clamp(value,
 10769        attrib.ClampMin ? (float)attrib.Min : float.MinValue,
 10770        attrib.ClampMax ? (float)attrib.Max : float.MaxValue);
 10771    }
 10772
 10773    private int Clamp(int value, RangeExAttribute attrib) {
 10774      return Mathf.Clamp(value,
 10775        attrib.ClampMin ? (int)attrib.Min : int.MinValue,
 10776        attrib.ClampMax ? (int)attrib.Max : int.MaxValue);
 10777    }
 10778
 10779    float DrawValue(SerializedProperty property, Rect position, GUIContent label, float floatValue) {
 10780      if (property.propertyType == SerializedPropertyType.Float) {
 10781        return EditorGUI.FloatField(position, label, floatValue);
 10782      } else {
 10783        DrawFloatValue(property, position, label, ref floatValue);
 10784        return floatValue;
 10785      }
 10786    }
 10787
 10788    int DrawValue(SerializedProperty property, Rect position, GUIContent label, int intValue) {
 10789      if (property.propertyType == SerializedPropertyType.Integer) {
 10790        return EditorGUI.IntField(position, label, intValue);
 10791      } else {
 10792        DrawIntValue(property, position, label, ref intValue);
 10793        return intValue;
 10794      }
 10795    }
 10796  }
 10797}
 10798
 10799#endregion
 10800
 10801
 10802#region ReadOnlyAttributeDrawer.cs
 10803
 10804namespace Fusion.Editor {
 10805  using UnityEditor;
 10806  using UnityEngine;
 10807
 10808  internal partial class ReadOnlyAttributeDrawer : DecoratingPropertyAttributeDrawer, INonApplicableOnArrayElements {
 010809    protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 010810      var  attribute  = (ReadOnlyAttribute)this.attribute;
 010811      bool isPlayMode = EditorApplication.isPlayingOrWillChangePlaymode;
 10812
 010813      using (new EditorGUI.DisabledGroupScope(isPlayMode ? attribute.InPlayMode : attribute.InEditMode)) {
 010814        base.OnGUIInternal(position, property, label);
 010815      }
 010816    }
 10817  }
 10818
 10819  [CustomPropertyDrawer(typeof(ReadOnlyAttribute))]
 10820  [RedirectCustomPropertyDrawer(typeof(ReadOnlyAttribute), typeof(ReadOnlyAttributeDrawer))]
 10821  partial class PropertyDrawerForArrayWorkaround {
 10822  }
 10823}
 10824
 10825#endregion
 10826
 10827
 10828#region ScenePathAttributeDrawer.cs
 10829
 10830namespace Fusion.Editor {
 10831  using System.Linq;
 10832  using UnityEditor;
 10833  using UnityEngine;
 10834
 10835  [CustomPropertyDrawer(typeof(ScenePathAttribute))]
 10836  internal class ScenePathAttributeDrawer : PropertyDrawerWithErrorHandling {
 10837    private SceneAsset[] _allScenes;
 10838
 10839    protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 10840      var oldScene = AssetDatabase.LoadAssetAtPath<SceneAsset>(property.stringValue);
 10841      if (oldScene == null && !string.IsNullOrEmpty(property.stringValue)) {
 10842        // well, maybe by name then?
 10843        _allScenes = _allScenes ?? AssetDatabase.FindAssets("t:scene")
 10844         .Select(x => AssetDatabase.GUIDToAssetPath(x))
 10845         .Select(x => AssetDatabase.LoadAssetAtPath<SceneAsset>(x))
 10846         .ToArray();
 10847
 10848        var matchedByName = _allScenes.Where(x => x.name == property.stringValue).ToList();
 10849        ;
 10850
 10851        if (matchedByName.Count == 0) {
 10852          SetError($"Scene not found: {property.stringValue}");
 10853        } else {
 10854          oldScene = matchedByName[0];
 10855          if (matchedByName.Count > 1) {
 10856            SetWarning("There are multiple scenes with this name");
 10857          }
 10858        }
 10859      }
 10860
 10861      using (new FusionEditorGUI.PropertyScope(position, label, property)) {
 10862        EditorGUI.BeginChangeCheck();
 10863        var newScene = EditorGUI.ObjectField(position, label, oldScene, typeof(SceneAsset), false) as SceneAsset;
 10864        if (EditorGUI.EndChangeCheck()) {
 10865          var assetPath = AssetDatabase.GetAssetPath(newScene);
 10866          property.stringValue = assetPath;
 10867          property.serializedObject.ApplyModifiedProperties();
 10868          ClearError();
 10869        }
 10870      }
 10871    }
 10872  }
 10873}
 10874
 10875#endregion
 10876
 10877
 10878#region ScriptFieldDrawer.cs
 10879
 10880namespace Fusion.Editor {
 10881  using UnityEditor;
 10882  using UnityEngine;
 10883
 10884  internal class ScriptFieldDrawer : PropertyDrawer {
 10885
 10886    private new ScriptHelpAttribute attribute => (ScriptHelpAttribute)base.attribute;
 10887
 10888    public bool ForceHide = false;
 10889
 10890    private bool       _initialized;
 10891    private GUIContent _helpContent;
 10892    private GUIContent _headerContent;
 10893
 10894    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
 10895
 10896      if (ForceHide || attribute?.Hide == true) {
 10897        return;
 10898      }
 10899
 10900      if (attribute == null) {
 10901        EditorGUI.PropertyField(position, property, label);
 10902        return;
 10903      }
 10904
 10905
 10906      EnsureInitialized(property);
 10907
 10908      var  helpButtonRect  = FusionEditorGUI.GetInlineHelpButtonRect(position, false);
 10909      bool wasHelpExpanded = _helpContent != null && FusionEditorGUI.IsHelpExpanded(this, property.propertyPath);
 10910
 10911      if (wasHelpExpanded) {
 10912        position = FusionEditorGUI.DrawInlineBoxUnderProperty(_helpContent, position, FusionEditorSkin.HelpInlineBoxColo
 10913      }
 10914
 10915      if (_helpContent != null) {
 10916        using (new FusionEditorGUI.EnabledScope(true)) {
 10917          if (FusionEditorGUI.DrawInlineHelpButton(helpButtonRect, wasHelpExpanded, true, false)) {
 10918            FusionEditorGUI.SetHelpExpanded(this, property.propertyPath, !wasHelpExpanded);
 10919          }
 10920        }
 10921      }
 10922
 10923      if (attribute.Style == ScriptHeaderStyle.Unity) {
 10924        EditorGUI.PropertyField(position, property, label);
 10925      } else {
 10926        using (new FusionEditorGUI.EnabledScope(true)) {
 10927          if (attribute.BackColor != ScriptHeaderBackColor.None) {
 10928            FusionEditorGUI.DrawScriptHeaderBackground(position, FusionEditorSkin.GetScriptHeaderColor(attribute.BackCol
 10929          }
 10930
 10931          var labelPosition = FusionEditorSkin.ScriptHeaderLabelStyle.margin.Remove(position);
 10932          EditorGUIUtility.AddCursorRect(labelPosition, MouseCursor.Link);
 10933          EditorGUI.LabelField(labelPosition, _headerContent, FusionEditorSkin.ScriptHeaderLabelStyle);
 10934
 10935          var e = Event.current;
 10936          if (e.type == EventType.MouseDown && position.Contains(e.mousePosition)) {
 10937            if (e.clickCount == 1) {
 10938              if (!string.IsNullOrEmpty(attribute.Url)) {
 10939                Application.OpenURL(attribute.Url);
 10940              }
 10941
 10942              EditorGUIUtility.PingObject(property.objectReferenceValue);
 10943            } else {
 10944              AssetDatabase.OpenAsset(property.objectReferenceValue);
 10945            }
 10946          }
 10947
 10948          FusionEditorGUI.DrawScriptHeaderIcon(position);
 10949        }
 10950      }
 10951
 10952      if (_helpContent != null) {
 10953        using (new FusionEditorGUI.EnabledScope(true)) {
 10954          // paint over what the inspector has drawn
 10955          FusionEditorGUI.DrawInlineHelpButton(helpButtonRect, wasHelpExpanded, false, true);
 10956        }
 10957      }
 10958    }
 10959
 10960    public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
 10961
 10962      if (ForceHide || attribute?.Hide == true) {
 10963        return -EditorGUIUtility.standardVerticalSpacing;
 10964      }
 10965
 10966      if (attribute == null) {
 10967        return EditorGUIUtility.singleLineHeight;
 10968      }
 10969
 10970      var height = EditorGUIUtility.singleLineHeight;
 10971
 10972      if (FusionEditorGUI.IsHelpExpanded(this, property.propertyPath) && _helpContent != null) {
 10973        height += FusionEditorGUI.GetInlineBoxSize(_helpContent).y;
 10974      }
 10975
 10976      return height;
 10977    }
 10978
 10979    private void EnsureInitialized(SerializedProperty property) {
 10980      if (_initialized) {
 10981        return;
 10982      }
 10983
 10984      _initialized = true;
 10985
 10986      var type     = property.serializedObject.targetObject.GetType();
 10987
 10988      _headerContent = new GUIContent(ObjectNames.NicifyVariableName(type.Name).ToUpper());
 10989      _helpContent   = FusionCodeDoc.FindEntry(type);
 10990    }
 10991  }
 10992}
 10993
 10994#endregion
 10995
 10996
 10997#region SerializableTypeDrawer.cs
 10998
 10999namespace Fusion.Editor {
 11000  using System;
 11001  using UnityEditor;
 11002  using UnityEngine;
 11003  using UnityEngine.Scripting;
 11004
 11005  [CustomPropertyDrawer(typeof(SerializableType<>))]
 11006  [CustomPropertyDrawer(typeof(SerializableType))]
 11007  [CustomPropertyDrawer(typeof(SerializableTypeAttribute))]
 11008  internal class SerializableTypeDrawer : PropertyDrawerWithErrorHandling {
 11009    protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 11010
 11011      var attr = (SerializableTypeAttribute)attribute;
 11012
 11013      SerializedProperty valueProperty;
 11014      if (property.propertyType == SerializedPropertyType.String) {
 11015        FusionEditorLog.Assert(attr != null);
 11016        valueProperty = property;
 11017      } else {
 11018        FusionEditorLog.Assert(property.propertyType == SerializedPropertyType.Generic);
 11019        valueProperty = property.FindPropertyRelativeOrThrow(nameof(SerializableType.AssemblyQualifiedName));
 11020      }
 11021
 11022      var assemblyQualifiedName = valueProperty.stringValue;
 11023
 11024      var baseType     = typeof(object);
 11025      var leafType     = fieldInfo.FieldType.GetUnityLeafType();
 11026      if (leafType.IsGenericType && leafType.GetGenericTypeDefinition() == typeof(SerializableType<>)) {
 11027        baseType = leafType.GetGenericArguments()[0];
 11028      }
 11029      if (attr?.BaseType != null) {
 11030        baseType = attr.BaseType;
 11031      }
 11032
 11033      position = EditorGUI.PrefixLabel(position, label);
 11034
 11035      string content = "[None]";
 11036      if (!string.IsNullOrEmpty(assemblyQualifiedName)) {
 11037        try {
 11038          var type = Type.GetType(assemblyQualifiedName, true);
 11039          content = type.FullName;
 11040
 11041          if (attr?.WarnIfNoPreserveAttribute == true) {
 11042            if (!type.IsDefined(typeof(PreserveAttribute), false)) {
 11043              SetWarning($"Please mark {type.FullName} with [Preserve] attribute to prevent it from being stripped from 
 11044            }
 11045          }
 11046        } catch (Exception e) {
 11047          SetError(e);
 11048          content = assemblyQualifiedName;
 11049        }
 11050      }
 11051
 11052      if (EditorGUI.DropdownButton(position, new GUIContent(content), FocusType.Keyboard)) {
 11053        ClearError();
 11054        FusionEditorGUI.DisplayTypePickerMenu(position, baseType, t => {
 11055          string typeName = string.Empty;
 11056          if (t != null) {
 11057            typeName = attr?.UseFullAssemblyQualifiedName == false ? SerializableType.GetShortAssemblyQualifiedName(t) :
 11058          }
 11059
 11060          valueProperty.stringValue = typeName;
 11061          valueProperty.serializedObject.ApplyModifiedProperties();
 11062        });
 11063      }
 11064    }
 11065  }
 11066}
 11067
 11068#endregion
 11069
 11070
 11071#region SerializeReferenceTypePickerAttributeDrawer.cs
 11072
 11073namespace Fusion.Editor {
 11074  using System.Linq;
 11075  using UnityEditor;
 11076  using UnityEngine;
 11077
 11078  [CustomPropertyDrawer(typeof(SerializeReferenceTypePickerAttribute))]
 11079  partial class SerializeReferenceTypePickerAttributeDrawer : DecoratingPropertyAttributeDrawer {
 11080
 11081    const string NullContent = "Null";
 11082
 11083    protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 11084
 11085      var attribute = (SerializeReferenceTypePickerAttribute)this.attribute;
 11086
 11087      Rect pickerRect;
 11088      if (label == GUIContent.none) {
 11089        pickerRect = position;
 11090        pickerRect.height = EditorGUIUtility.singleLineHeight;
 11091      } else {
 11092        pickerRect = EditorGUI.PrefixLabel(new Rect(position) { height = EditorGUIUtility.singleLineHeight }, FusionEdit
 11093      }
 11094
 11095      object instance = property.managedReferenceValue;
 11096      var instanceType = instance?.GetType();
 11097
 11098      if (EditorGUI.DropdownButton(pickerRect, new GUIContent(instanceType?.FullName ?? NullContent), FocusType.Keyboard
 11099
 11100        var types = attribute.Types;
 11101        if (!types.Any()) {
 11102          types = new[] { fieldInfo.FieldType.GetUnityLeafType() };
 11103        }
 11104
 11105        FusionEditorGUI.DisplayTypePickerMenu(pickerRect, types,
 11106          t => {
 11107            if (t == null) {
 11108              instance = null;
 11109            } else if (t.IsInstanceOfType(instance)) {
 11110              // do nothing
 11111              return;
 11112            } else {
 11113              instance = System.Activator.CreateInstance(t);
 11114            }
 11115            property.managedReferenceValue = instance;
 11116            property.serializedObject.ApplyModifiedProperties();
 11117          },
 11118          noneOptionLabel: NullContent,
 11119          selectedType: instanceType,
 11120          flags: (attribute.GroupTypesByNamespace ? FusionEditorGUIDisplayTypePickerMenuFlags.GroupByNamespace : 0) | (a
 11121      }
 11122
 11123      base.OnGUIInternal(position, property, label);
 11124    }
 11125  }
 11126}
 11127
 11128#endregion
 11129
 11130
 11131#region ToggleLeftAttributeDrawer.cs
 11132
 11133namespace Fusion.Editor {
 11134  using UnityEditor;
 11135  using UnityEngine;
 11136
 11137  [CustomPropertyDrawer(typeof(ToggleLeftAttribute))]
 11138  internal class ToggleLeftAttributeDrawer : PropertyDrawer {
 11139    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
 11140      EditorGUI.BeginProperty(position, label, property);
 11141
 11142      EditorGUI.BeginChangeCheck();
 11143      var val = EditorGUI.ToggleLeft(position, label, property.boolValue);
 11144
 11145      if (EditorGUI.EndChangeCheck()) {
 11146        property.boolValue = val;
 11147      }
 11148
 11149      EditorGUI.EndProperty();
 11150    }
 11151  }
 11152}
 11153
 11154#endregion
 11155
 11156
 11157#region UnitAttributeDrawer.cs
 11158
 11159namespace Fusion.Editor {
 11160  using System;
 11161  using System.Reflection;
 11162  using UnityEditor;
 11163  using UnityEngine;
 11164
 11165  [CustomPropertyDrawer(typeof(UnitAttribute))]
 11166  [FusionPropertyDrawerMeta(HandlesUnits = true)]
 11167  internal partial class UnitAttributeDrawer : DecoratingPropertyAttributeDrawer {
 11168    private GUIContent _label;
 11169
 11170    private void EnsureInitialized() {
 11171      if (_label == null) {
 11172        _label = new GUIContent(UnitToLabel(((UnitAttribute)attribute).Unit));
 11173      }
 11174    }
 11175
 11176    protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 11177      base.OnGUIInternal(position, property, label);
 11178
 11179      // check if any of the next drawers handles the unit
 11180      for (var nextDrawer = GetNextDrawer(property); nextDrawer != null; nextDrawer = (nextDrawer as DecoratingPropertyA
 11181        var meta = nextDrawer.GetType().GetCustomAttribute<FusionPropertyDrawerMetaAttribute>();
 11182        if (meta?.HandlesUnits == true) {
 11183          return;
 11184        }
 11185      }
 11186
 11187      EnsureInitialized();
 11188
 11189      var propertyType = property.propertyType;
 11190      var isExpanded  = property.isExpanded;
 11191
 11192      DrawUnitOverlay(position, _label, propertyType, isExpanded);
 11193    }
 11194
 11195    public static void DrawUnitOverlay(Rect position, GUIContent label, SerializedPropertyType propertyType, bool isExpa
 11196      switch (propertyType) {
 11197
 11198        case SerializedPropertyType.Vector2 when odinStyle:
 11199        case SerializedPropertyType.Vector3 when odinStyle:
 11200        case SerializedPropertyType.Vector4 when odinStyle: {
 11201          var pos = position;
 11202          int memberCount = (propertyType == SerializedPropertyType.Vector2) ? 2 :
 11203                            (propertyType == SerializedPropertyType.Vector3) ? 3 : 4;
 11204          pos.xMin   += EditorGUIUtility.labelWidth;
 11205          pos.yMin   =  pos.yMax - EditorGUIUtility.singleLineHeight;
 11206          pos.width  /= memberCount;
 11207          pos.height =  EditorGUIUtility.singleLineHeight;
 11208
 11209          for (int i = 0; i < memberCount; ++i) {
 11210            FusionEditorGUI.Overlay(pos, label);
 11211            pos.x += pos.width;
 11212          }
 11213
 11214          break;
 11215        }
 11216
 11217        case SerializedPropertyType.Vector2:
 11218        case SerializedPropertyType.Vector3: {
 11219          Rect pos = position;
 11220          // vector properties get broken down into two lines when there's not enough space
 11221          if (EditorGUIUtility.wideMode) {
 11222            pos.xMin  += EditorGUIUtility.labelWidth;
 11223            pos.width /= 3;
 11224          } else {
 11225            pos.xMin  += 12;
 11226            pos.yMin  =  pos.yMax - EditorGUIUtility.singleLineHeight;
 11227            pos.width /= (propertyType == SerializedPropertyType.Vector2) ? 2 : 3;
 11228          }
 11229
 11230          pos.height = EditorGUIUtility.singleLineHeight;
 11231          FusionEditorGUI.Overlay(pos, label);
 11232          pos.x += pos.width;
 11233          FusionEditorGUI.Overlay(pos, label);
 11234          if (propertyType == SerializedPropertyType.Vector3) {
 11235            pos.x += pos.width;
 11236            FusionEditorGUI.Overlay(pos, label);
 11237          }
 11238
 11239          break;
 11240        }
 11241        case SerializedPropertyType.Vector4:
 11242          if (isExpanded) {
 11243            Rect pos = position;
 11244            pos.yMin   = pos.yMax - 4 * EditorGUIUtility.singleLineHeight - 3 * EditorGUIUtility.standardVerticalSpacing
 11245            pos.height = EditorGUIUtility.singleLineHeight;
 11246            for (int i = 0; i < 4; ++i) {
 11247              FusionEditorGUI.Overlay(pos, label);
 11248              pos.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
 11249            }
 11250          }
 11251
 11252          break;
 11253        default: {
 11254          var pos = position;
 11255          pos.height = EditorGUIUtility.singleLineHeight;
 11256          FusionEditorGUI.Overlay(pos, label);
 11257        }
 11258          break;
 11259      }
 11260    }
 11261
 11262    public static string UnitToLabel(Units units) {
 11263      switch (units) {
 11264        case Units.None:                 return string.Empty;
 11265        case Units.Ticks:                return "ticks";
 11266        case Units.Seconds:              return "s";
 11267        case Units.MilliSecs:            return "ms";
 11268        case Units.Kilobytes:            return "kB";
 11269        case Units.Megabytes:            return "MB";
 11270        case Units.Normalized:           return "normalized";
 11271        case Units.Multiplier:           return "multiplier";
 11272        case Units.Percentage:           return "%";
 11273        case Units.NormalizedPercentage: return "n%";
 11274        case Units.Degrees:              return "\u00B0";
 11275        case Units.PerSecond:            return "hz";
 11276        case Units.DegreesPerSecond:     return "\u00B0/sec";
 11277        case Units.Radians:              return "rad";
 11278        case Units.RadiansPerSecond:     return "rad/s";
 11279        case Units.TicksPerSecond:       return "ticks/s";
 11280        case Units.Units:                return "units";
 11281        case Units.Bytes:                return "B";
 11282        case Units.Count:                return "count";
 11283        case Units.Packets:              return "packets";
 11284        case Units.Frames:               return "frames";
 11285        case Units.FramesPerSecond:      return "fps";
 11286        case Units.SquareMagnitude:      return "mag\u00B2";
 11287        default:                         throw new ArgumentOutOfRangeException(nameof(units), $"{units}");
 11288      }
 11289    }
 11290  }
 11291}
 11292
 11293#endregion
 11294
 11295
 11296#region UnityAddressablesRuntimeKeyAttributeDrawer.cs
 11297
 11298#if (FUSION_ADDRESSABLES || FUSION_ENABLE_ADDRESSABLES) && !FUSION_DISABLE_ADDRESSABLES
 11299namespace Fusion.Editor {
 11300  using UnityEditor;
 11301  using UnityEngine;
 11302  using Object = UnityEngine.Object;
 11303
 11304  [CustomPropertyDrawer(typeof(UnityAddressablesRuntimeKeyAttribute))]
 11305  internal class UnityAddressablesRuntimeKeyAttributeDrawer : PropertyDrawerWithErrorHandling {
 11306    protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 11307      var attrib = (UnityAddressablesRuntimeKeyAttribute)attribute;
 11308
 11309      using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out position)) {
 11310        position.width -= 40;
 11311        EditorGUI.PropertyField(position, property, GUIContent.none, false);
 11312        Object asset = null;
 11313
 11314        var runtimeKey = property.stringValue;
 11315
 11316        if (!string.IsNullOrEmpty(runtimeKey)) {
 11317          if (!FusionAddressablesUtils.TryParseAddress(runtimeKey, out var _, out var _)) {
 11318            SetError($"Not a valid address: {runtimeKey}");
 11319          } else {
 11320            asset = FusionAddressablesUtils.LoadEditorInstance(runtimeKey);
 11321            if (asset == null) {
 11322              SetError($"Asset not found for runtime key: {runtimeKey}");
 11323            }
 11324          }
 11325        }
 11326
 11327        using (new FusionEditorGUI.EnabledScope(asset)) {
 11328          position.x     += position.width;
 11329          position.width =  40;
 11330          if (GUI.Button(position, "Ping")) {
 11331            EditorGUIUtility.PingObject(asset);
 11332          }
 11333        }
 11334      }
 11335    }
 11336  }
 11337}
 11338#endif
 11339
 11340#endregion
 11341
 11342
 11343#region UnityAssetGuidAttributeDrawer.cs
 11344
 11345namespace Fusion.Editor {
 11346  using System;
 11347  using UnityEditor;
 11348  using UnityEngine;
 11349
 11350  [CustomPropertyDrawer(typeof(UnityAssetGuidAttribute))]
 11351  [FusionPropertyDrawerMeta(HasFoldout = false)]
 11352  internal class UnityAssetGuidAttributeDrawer : PropertyDrawerWithErrorHandling {
 11353    protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 11354      string guid;
 11355      position.width -= 40;
 11356
 11357      if (property.propertyType == SerializedPropertyType.Generic) {
 11358        guid = DrawMangledRawGuid(position, property, label);
 11359      } else if (property.propertyType == SerializedPropertyType.String) {
 11360        using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out position)) {
 11361          EditorGUI.PropertyField(position, property, GUIContent.none, false);
 11362          guid = property.stringValue;
 11363        }
 11364      } else {
 11365        throw new InvalidOperationException();
 11366      }
 11367
 11368      string assetPath = string.Empty;
 11369
 11370      bool parsable = GUID.TryParse(guid, out _);
 11371      if (parsable) {
 11372        ClearError();
 11373        assetPath = AssetDatabase.GUIDToAssetPath(guid);
 11374      }
 11375
 11376      using (new FusionEditorGUI.EnabledScope(!string.IsNullOrEmpty(assetPath))) {
 11377        position.x     += position.width;
 11378        position.width =  40;
 11379
 11380        if (GUI.Button(position, "Ping")) {
 11381          EditorGUIUtility.PingObject(AssetDatabase.LoadMainAssetAtPath(assetPath));
 11382        }
 11383      }
 11384
 11385      if (string.IsNullOrEmpty(assetPath)) {
 11386        if (!parsable && !string.IsNullOrEmpty(guid)) {
 11387          SetError($"Invalid GUID: {guid}");
 11388        } else if (!string.IsNullOrEmpty(guid)) {
 11389          SetWarning($"GUID not found");
 11390        }
 11391      } else {
 11392        var asset = AssetDatabase.LoadMainAssetAtPath(assetPath);
 11393        if (asset == null) {
 11394          SetError($"Asset with this guid does not exist. Last path:\n{assetPath}");
 11395        } else {
 11396          SetInfo($"Asset path:\n{assetPath}");
 11397        }
 11398      }
 11399    }
 11400
 11401    private unsafe string DrawMangledRawGuid(Rect position, SerializedProperty property, GUIContent label) {
 11402      var inner = property.Copy();
 11403      inner.Next(true);
 11404      if (inner.depth != property.depth + 1 || !inner.isFixedBuffer || inner.fixedBufferSize != 2) {
 11405        throw new InvalidOperationException();
 11406      }
 11407
 11408      var prop0 = inner.GetFixedBufferElementAtIndex(0);
 11409      var prop1 = inner.GetFixedBufferElementAtIndex(1);
 11410
 11411      string guid;
 11412      unsafe {
 11413        var rawMangled = stackalloc long[2];
 11414        rawMangled[0] = prop0.longValue;
 11415        rawMangled[1] = prop1.longValue;
 11416
 11417        Guid guidStruct = default;
 11418        CopyAndMangleGuid((byte*)rawMangled, (byte*)&guidStruct);
 11419
 11420        using (new FusionEditorGUI.PropertyScope(position, label, property)) {
 11421          EditorGUI.BeginChangeCheck();
 11422          guid = EditorGUI.TextField(position, label, guidStruct.ToString("N"));
 11423          if (EditorGUI.EndChangeCheck()) {
 11424            if (Guid.TryParse(guid, out guidStruct)) {
 11425              CopyAndMangleGuid((byte*)&guidStruct, (byte*)rawMangled);
 11426              prop0.longValue = rawMangled[0];
 11427              prop1.longValue = rawMangled[1];
 11428            } else {
 11429              SetError($"Unable to parse {guid}");
 11430            }
 11431          }
 11432        }
 11433      }
 11434
 11435      return guid;
 11436    }
 11437
 11438    public static unsafe void CopyAndMangleGuid(byte* src, byte* dst) {
 11439      dst[0] = src[3];
 11440      dst[1] = src[2];
 11441      dst[2] = src[1];
 11442      dst[3] = src[0];
 11443
 11444      dst[4] = src[5];
 11445      dst[5] = src[4];
 11446
 11447      dst[6] = src[7];
 11448      dst[7] = src[6];
 11449
 11450      dst[8]  = src[8];
 11451      dst[9]  = src[9];
 11452      dst[10] = src[10];
 11453      dst[11] = src[11];
 11454      dst[12] = src[12];
 11455      dst[13] = src[13];
 11456      dst[14] = src[14];
 11457      dst[15] = src[15];
 11458    }
 11459
 11460    public bool HasFoldout(SerializedProperty property) {
 11461      return false;
 11462    }
 11463  }
 11464}
 11465
 11466#endregion
 11467
 11468
 11469#region UnityResourcePathAttributeDrawer.cs
 11470
 11471namespace Fusion.Editor {
 11472  using UnityEditor;
 11473  using UnityEngine;
 11474
 11475  [CustomPropertyDrawer(typeof(UnityResourcePathAttribute))]
 11476  internal class UnityResourcePathAttributeDrawer : PropertyDrawerWithErrorHandling {
 11477    protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
 11478      var attrib = (UnityResourcePathAttribute)attribute;
 11479
 11480      using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out position)) {
 11481        position.width -= 40;
 11482        EditorGUI.PropertyField(position, property, GUIContent.none, false);
 11483        Object asset = null;
 11484
 11485        var path = property.stringValue;
 11486        if (string.IsNullOrEmpty(path)) {
 11487          ClearError();
 11488        } else {
 11489          asset = Resources.Load(path, attrib.ResourceType);
 11490          if (asset == null) {
 11491            SetError($"Resource of type {attrib.ResourceType} not found at {path}");
 11492          } else {
 11493            SetInfo(AssetDatabase.GetAssetPath(asset));
 11494          }
 11495        }
 11496
 11497        using (new FusionEditorGUI.EnabledScope(asset)) {
 11498          position.x     += position.width;
 11499          position.width =  40;
 11500          if (GUI.Button(position, "Ping")) {
 11501            EditorGUIUtility.PingObject(asset);
 11502          }
 11503        }
 11504      }
 11505    }
 11506  }
 11507}
 11508
 11509#endregion
 11510
 11511
 11512#region WarnIfAttributeDrawer.cs
 11513
 11514namespace Fusion.Editor {
 11515  using UnityEditor;
 11516  using UnityEngine;
 11517
 11518  partial class WarnIfAttributeDrawer : MessageIfDrawerBase {
 11519    private new WarnIfAttribute Attribute   => (WarnIfAttribute)attribute;
 11520
 11521    protected override bool        IsBox          => Attribute.AsBox;
 11522    protected override string      Message        => Attribute.Message;
 11523    protected override MessageType MessageType    => MessageType.Warning;
 11524    protected override Color       InlineBoxColor => FusionEditorSkin.WarningInlineBoxColor;
 11525    protected override Texture     MessageIcon    => FusionEditorSkin.WarningIcon;
 11526  }
 11527
 11528  [CustomPropertyDrawer(typeof(WarnIfAttribute))]
 11529  [RedirectCustomPropertyDrawer(typeof(WarnIfAttribute), typeof(WarnIfAttributeDrawer))]
 11530  partial class PropertyDrawerForArrayWorkaround {
 11531  }
 11532}
 11533
 11534
 11535#endregion
 11536
 11537
 11538
 11539#endregion
 11540
 11541
 11542#region Assets/Photon/Fusion/Editor/FusionHierarchyWindowOverlay.cs
 11543
 11544namespace Fusion.Editor {
 11545  using System;
 11546  using Fusion.Analyzer;
 11547  using UnityEditor;
 11548  using UnityEngine;
 11549  using UnityEngine.SceneManagement;
 11550
 11551  internal class FusionHierarchyWindowOverlay {
 11552
 11553    [RuntimeInitializeOnLoadMethod]
 11554    public static void Initialize() {
 11555      UnityEditor.EditorApplication.hierarchyWindowItemOnGUI -= HierarchyWindowOverlay;
 11556      UnityEditor.EditorApplication.hierarchyWindowItemOnGUI += HierarchyWindowOverlay;
 11557    }
 11558
 11559    [StaticField(StaticFieldResetMode.None)]
 11560    private static Lazy<GUIStyle> s_hierarchyOverlayLabelStyle = new Lazy<GUIStyle>(() => {
 11561      var result = new GUIStyle(UnityEditor.EditorStyles.miniButton);
 11562      result.alignment = TextAnchor.MiddleCenter;
 11563      result.fontSize = 9;
 11564      result.padding = new RectOffset(4, 4, 0, 0);
 11565      result.fixedHeight = 13f;
 11566      return result;
 11567    });
 11568
 11569    [StaticField(StaticFieldResetMode.None)]
 11570    private static GUIContent s_multipleInstancesContent = EditorGUIUtility.IconContent("Warning", "multiple");
 11571
 11572    private static void HierarchyWindowOverlay(int instanceId, Rect position) {
 11573      var obj = UnityEditor.EditorUtility.InstanceIDToObject(instanceId);
 11574      if (obj != null) {
 11575        return;
 11576      }
 11577
 11578      // find a scene for this id
 11579      Scene scene = default;
 11580      for (int i = 0; i < SceneManager.sceneCount; ++i) {
 11581        var s = SceneManager.GetSceneAt(i);
 11582        if (s.handle == instanceId) {
 11583          scene = s;
 11584          break;
 11585        }
 11586      }
 11587
 11588      if (!scene.IsValid()) {
 11589        return;
 11590      }
 11591
 11592      var instances = NetworkRunner.Instances;
 11593
 11594      NetworkRunner matchingRunner = null;
 11595      bool multipleRunners = false;
 11596
 11597      for (int i = 0; i < instances.Count; ++i) {
 11598        var runner = instances[i];
 11599
 11600        if (runner.SimulationUnityScene == scene) {
 11601          if (matchingRunner == null) {
 11602            matchingRunner = runner;
 11603          } else {
 11604            multipleRunners = true;
 11605            break;
 11606          }
 11607        }
 11608      }
 11609
 11610      if (!matchingRunner) {
 11611        return;
 11612      }
 11613
 11614      var rect = new Rect(position) {
 11615        xMin = position.xMax - 56,
 11616        xMax = position.xMax - 2,
 11617        yMin = position.yMin + 1,
 11618      };
 11619
 11620      {
 11621        if (multipleRunners) {
 11622          if (EditorGUI.DropdownButton(rect, s_multipleInstancesContent, FocusType.Passive, s_hierarchyOverlayLabelStyle
 11623            var menu = new GenericMenu();
 11624            for (int i = 0; i < instances.Count; ++i) {
 11625              var runner = instances[i];
 11626              var otherScene = runner.SimulationUnityScene;
 11627              if (!otherScene.IsValid()) {
 11628                continue;
 11629              }
 11630              if (otherScene.handle == instanceId) {
 11631                menu.AddItem(MakeRunnerContent(runner), false, () => {
 11632                  EditorGUIUtility.PingObject(runner);
 11633                  Selection.activeObject = runner;
 11634                });
 11635              }
 11636            }
 11637            menu.ShowAsContext();
 11638          }
 11639        } else {
 11640          var runner = matchingRunner;
 11641          if (GUI.Button(rect, MakeRunnerContent(runner), s_hierarchyOverlayLabelStyle.Value)) {
 11642            EditorGUIUtility.PingObject(runner);
 11643            Selection.activeGameObject = runner.gameObject;
 11644          }
 11645        }
 11646      }
 11647
 11648      GUIContent MakeRunnerContent(NetworkRunner runner) {
 11649        return new GUIContent($"{runner.Mode} {(runner.LocalPlayer.IsRealPlayer ? "P" + runner.LocalPlayer.PlayerId.ToSt
 11650      }
 11651    }
 11652  }
 11653}
 11654
 11655
 11656#endregion
 11657
 11658
 11659#region Assets/Photon/Fusion/Editor/FusionHubWindowUtils.cs
 11660
 11661// ----------------------------------------------------------------------------
 11662// <copyright file="WizardWindowUtils.cs" company="Exit Games GmbH">
 11663//   PhotonNetwork Framework for Unity - Copyright (C) 2021 Exit Games GmbH
 11664// </copyright>
 11665// <summary>
 11666//   MenuItems and in-Editor scripts for PhotonNetwork.
 11667// </summary>
 11668// <author>developer@exitgames.com</author>
 11669// ----------------------------------------------------------------------------
 11670
 11671namespace Fusion.Editor {
 11672#if FUSION_WEAVER && UNITY_EDITOR
 11673  using System;
 11674  using System.Collections.Generic;
 11675  using System.ComponentModel;
 11676  using System.IO;
 11677  using System.Text.RegularExpressions;
 11678  using Photon.Realtime;
 11679  using UnityEditor;
 11680  using UnityEngine;
 11681  using UnityEngine.UI;
 11682
 11683  public partial class FusionHubWindow {
 11684    /// <summary>
 11685    /// Section Definition.
 11686    /// </summary>
 11687    internal class Section {
 11688      public string Title;
 11689      public string Description;
 11690      public Action DrawMethod;
 11691      public Icon Icon;
 11692
 11693      public Section(string title, string description, Action drawMethod, Icon icon) {
 11694        Title = title;
 11695        Description = description;
 11696        DrawMethod = drawMethod;
 11697        Icon = icon;
 11698      }
 11699    }
 11700
 11701    public enum Icon {
 11702      Setup,
 11703      Documentation,
 11704      Samples,
 11705      Community,
 11706      ProductLogo,
 11707      PhotonCloud,
 11708      FusionIcon,
 11709    }
 11710
 11711    private static class Constants {
 11712      public const string UrlFusionDocsOnline = "https://doc.photonengine.com/fusion/v2/";
 11713      public const string UrlFusionIntro = "https://doc.photonengine.com/fusion/v2/getting-started/fusion-introduction";
 11714      public const string UrlFusionSDK = "https://doc.photonengine.com/fusion/v2/getting-started/sdk-download";
 11715      public const string UrlCloudDashboard = "https://id.photonengine.com/account/signin?email=";
 11716      public const string UrlDashboardProfile = "https://dashboard.photonengine.com/Account/Profile";
 11717      public const string UrlDashboard = "https://dashboard.photonengine.com/";
 11718      public const string UrlSampleSection = "https://doc.photonengine.com/fusion/v2/current/samples/overview";
 11719      public const string UrlFusion100 = "https://doc.photonengine.com/fusion/v2/tutorials/shared-mode-basics/overview";
 11720      public const string UrlFusionLoop = "https://doc.photonengine.com/fusion/v2/current/samples/fusion-application-loo
 11721      public const string UrlHelloFusion = "https://doc.photonengine.com/fusion/v2/current/hello-fusion/hello-fusion";
 11722      public const string UrlHelloFusionVr = "https://doc.photonengine.com/fusion/v2/current/hello-fusion/hello-fusion-v
 11723      public const string UrlTanks = "https://doc.photonengine.com/fusion/v2/current/samples/fusion-tanknarok";
 11724      public const string UrlKarts = "https://doc.photonengine.com/fusion/v2/current/samples/fusion-karts";
 11725      public const string UrlDragonHuntersVR = "https://doc.photonengine.com/fusion/v2/current/samples/fusion-dragonhunt
 11726
 11727      public const string UrlFusionDocApi = "https://doc-api.photonengine.com/en/fusion/v2/index.html";
 11728      public const string WindowTitle = "Photon Fusion 2 Hub";
 11729      public const string Support = "You can contact the Photon Team using one of the following links. You can also go t
 11730      public const string DiscordText = "Create a Photon account and join the Discord.";
 11731      public const string DiscordHeader = "Community";
 11732      public const string DocumentationText = "Open the documentation.";
 11733      public const string DocumentationHeader = "Documentation";
 11734
 11735      public const string WelcomeText = "Thank you for installing Photon Fusion 2.\n\n" +
 11736                                        "Once you have set up your Fusion 2 App Id, explore the sections on the left to 
 11737                                        "More samples, tutorials, and documentation are being added regularly - so check
 11738
 11739      public const string RealtimeAppidSetupInstructions =
 11740        @"<b>An Fusion App Id Version 2 is required for networking.</b>
 11741
 11742  To acquire an Fusion App Id:
 11743  - Open the Photon Dashboard (Log-in as required).
 11744  - Select an existing Fusion 2 App Id, or;
 11745  - Create a new one (make sure to select SDK Version 2).
 11746  - Copy the App Id and paste into the field below (or into the PhotonAppSettings.asset).
 11747  ";
 11748
 11749      public const string GettingStartedInstructions =
 11750        @"Links to demos, tutorials, API references and other information can be found on the PhotonEngine.com website."
 11751    }
 11752
 11753    public Texture2D SetupIcon;
 11754    public Texture2D DocumentationIcon;
 11755    public Texture2D SamplesIcon;
 11756    public Texture2D CommunityIcon;
 11757    public Texture2D ProductLogo;
 11758    public Texture2D PhotonCloudIcon;
 11759    public Texture2D FusionIcon;
 11760    public Texture2D CorrectIcon;
 11761
 11762    private Texture2D GetIcon(Icon icon) {
 11763      switch (icon) {
 11764        case Icon.Setup: return SetupIcon;
 11765        case Icon.Documentation: return DocumentationIcon;
 11766        case Icon.Samples: return SamplesIcon;
 11767        case Icon.Community: return CommunityIcon;
 11768        case Icon.ProductLogo: return ProductLogo;
 11769        case Icon.PhotonCloud: return PhotonCloudIcon;
 11770        case Icon.FusionIcon: return FusionIcon;
 11771        default: return null;
 11772      }
 11773    }
 11774
 11775    [NonSerialized] private Section[] _sections;
 11776
 11777    private static string releaseHistoryHeader;
 11778    private static List<string> releaseHistoryTextAdded;
 11779    private static List<string> releaseHistoryTextChanged;
 11780    private static List<string> releaseHistoryTextFixed;
 11781    private static List<string> releaseHistoryTextRemoved;
 11782    private static List<string> releaseHistoryTextInternal;
 11783
 11784    private static string fusionReleaseHistory;
 11785
 11786    public GUISkin FusionHubSkin;
 11787
 11788    private static GUIStyle _navbarHeaderGraphicStyle;
 11789    private static GUIStyle textLabelStyle;
 11790    private static GUIStyle headerLabelStyle;
 11791    private static GUIStyle releaseNotesStyle;
 11792    private static GUIStyle headerTextStyle;
 11793    private static GUIStyle buttonActiveStyle;
 11794
 11795    private bool InitContent() {
 11796      if (_ready.HasValue && _ready.Value) {
 11797        return _ready.Value;
 11798      }
 11799
 11800      // skip while being loaded
 11801      if (FusionHubSkin == null) { return false; }
 11802
 11803      // Just need to run once
 11804      FusionGlobalScriptableObjectUtils.EnsureAssetExists<PhotonAppSettings>();
 11805      FusionGlobalScriptableObjectUtils.EnsureAssetExists<NetworkProjectConfigAsset>();
 11806
 11807      _sections = new[] {
 11808        new Section("Welcome", "Welcome to Photon Fusion 2", DrawWelcomeSection, Icon.Setup), new Section("Fusion 2 Setu
 11809        new Section("Tutorials & Samples", "Fusion Tutorials and Samples", DrawSamplesSection, Icon.Samples),
 11810        new Section("Documentation", "Photon Fusion Documentation", DrawDocumentationSection, Icon.Documentation),
 11811        new Section("Fusion Release Notes", "Fusion Release Notes", DrawFusionReleaseSection, Icon.Documentation),
 11812        new Section("Support", "Support and Community Links", DrawSupportSection, Icon.Community),
 11813      };
 11814
 11815      Color commonTextColor = Color.white;
 11816
 11817      var _guiSkin = FusionHubSkin;
 11818
 11819      _navbarHeaderGraphicStyle = new GUIStyle(_guiSkin.button) { alignment = TextAnchor.MiddleCenter };
 11820
 11821      headerTextStyle = new GUIStyle(_guiSkin.label) { fontSize = 18, padding = new RectOffset(12, 8, 8, 8), fontStyle =
 11822
 11823      buttonActiveStyle = new GUIStyle(_guiSkin.button) { fontStyle = FontStyle.Bold, normal = { background = _guiSkin.b
 11824
 11825
 11826      textLabelStyle = new GUIStyle(_guiSkin.label) { wordWrap = true, normal = { textColor = commonTextColor }, richTex
 11827      headerLabelStyle = new GUIStyle(textLabelStyle) { fontSize = 15, };
 11828
 11829      releaseNotesStyle = new GUIStyle(textLabelStyle) { richText = true, };
 11830
 11831      return (_ready = true).Value;
 11832    }
 11833
 11834    private static Action OpenURL(string url, params object[] args) {
 11835      return () => {
 11836        if (args.Length > 0) {
 11837          url = string.Format(url, args);
 11838        }
 11839
 11840        Application.OpenURL(url);
 11841      };
 11842    }
 11843
 11844    protected static bool IsAppIdValid() {
 11845      if (PhotonAppSettings.TryGetGlobal(out var global) && Guid.TryParse(global.AppSettings.AppIdFusion, out var guid))
 11846        return true;
 11847      }
 11848
 11849      return false;
 11850    }
 11851
 11852    static string titleVersionReformat, sectionReformat, header1Reformat, header2Reformat, header3Reformat, classReforma
 11853
 11854    void InitializeFormatters() {
 11855      titleVersionReformat = "<size=22><color=white>$1</color></size>";
 11856      sectionReformat = "<i><color=lightblue>$1</color></i>";
 11857      header1Reformat = "<size=22><color=white>$1</color></size>";
 11858      header2Reformat = "<size=18><color=white>$1</color></size>";
 11859      header3Reformat = "<b><color=#ffffaaff>$1</color></b>";
 11860      classReformat = "<color=#FFDDBB>$1</color>";
 11861    }
 11862
 11863    /// <summary>
 11864    /// Converts readme files into Unity RichText.
 11865    /// </summary>
 11866    private void PrepareReleaseHistoryText() {
 11867      if (sectionReformat == null || sectionReformat == "") {
 11868        InitializeFormatters();
 11869      }
 11870
 11871      // Fusion
 11872      {
 11873        try {
 11874          var filePath = BuildPath(Application.dataPath, "Photon", "Fusion", "release_history.txt");
 11875          var text = (TextAsset)AssetDatabase.LoadAssetAtPath(filePath, typeof(TextAsset));
 11876          var baseText = text.text;
 11877
 11878          // #
 11879          baseText = Regex.Replace(baseText, @"^# (.*)", titleVersionReformat);
 11880          baseText = Regex.Replace(baseText, @"(?<=\n)# (.*)", header1Reformat);
 11881          // ##
 11882          baseText = Regex.Replace(baseText, @"(?<=\n)## (.*)", header2Reformat);
 11883          // ###
 11884          baseText = Regex.Replace(baseText, @"(?<=\n)### (.*)", header3Reformat);
 11885          // **Changes**
 11886          baseText = Regex.Replace(baseText, @"(?<=\n)\*\*(.*)\*\*", sectionReformat);
 11887          // `Class`
 11888          baseText = Regex.Replace(baseText, @"\`([^\`]*)\`", classReformat);
 11889
 11890          fusionReleaseHistory = baseText;
 11891        } catch {
 11892          fusionReleaseHistory = "Unable to load Release History.";
 11893        }
 11894      }
 11895
 11896      // Realtime
 11897      {
 11898        try {
 11899          var filePath = BuildPath(Application.dataPath, "Photon", "PhotonRealtime", "Code", "changes-realtime.txt");
 11900
 11901          var text = (TextAsset)AssetDatabase.LoadAssetAtPath(filePath, typeof(TextAsset));
 11902
 11903          var baseText = text.text;
 11904
 11905          var regexVersion = new Regex(@"Version (\d+\.?)*", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | R
 11906          var regexAdded = new Regex(@"\b(Added:)(.*)\b", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | Rege
 11907          var regexChanged = new Regex(@"\b(Changed:)(.*)\b", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | 
 11908          var regexUpdated = new Regex(@"\b(Updated:)(.*)\b", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | 
 11909          var regexFixed = new Regex(@"\b(Fixed:)(.*)\b", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | Rege
 11910          var regexRemoved = new Regex(@"\b(Removed:)(.*)\b", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | 
 11911          var regexInternal = new Regex(@"\b(Internal:)(.*)\b", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant 
 11912
 11913          var matches = regexVersion.Matches(baseText);
 11914
 11915          if (matches.Count > 0) {
 11916            var currentVersionMatch = matches[0];
 11917            var lastVersionMatch = currentVersionMatch.NextMatch();
 11918
 11919            if (currentVersionMatch.Success && lastVersionMatch.Success) {
 11920              Func<MatchCollection, List<string>> itemProcessor = (match) => {
 11921                List<string> resultList = new List<string>();
 11922                for (int index = 0; index < match.Count; index++) {
 11923                  resultList.Add(match[index].Groups[2].Value.Trim());
 11924                }
 11925
 11926                return resultList;
 11927              };
 11928
 11929              string mainText = baseText.Substring(currentVersionMatch.Index + currentVersionMatch.Length,
 11930                lastVersionMatch.Index - lastVersionMatch.Length - 1).Trim();
 11931
 11932              releaseHistoryHeader = currentVersionMatch.Value.Trim();
 11933              releaseHistoryTextAdded = itemProcessor(regexAdded.Matches(mainText));
 11934              releaseHistoryTextChanged = itemProcessor(regexChanged.Matches(mainText));
 11935              releaseHistoryTextChanged.AddRange(itemProcessor(regexUpdated.Matches(mainText)));
 11936              releaseHistoryTextFixed = itemProcessor(regexFixed.Matches(mainText));
 11937              releaseHistoryTextRemoved = itemProcessor(regexRemoved.Matches(mainText));
 11938              releaseHistoryTextInternal = itemProcessor(regexInternal.Matches(mainText));
 11939            }
 11940          }
 11941        } catch {
 11942          releaseHistoryHeader = "\nPlease look the file changes-realtime.txt";
 11943          releaseHistoryTextAdded = new List<string>();
 11944          releaseHistoryTextChanged = new List<string>();
 11945          releaseHistoryTextFixed = new List<string>();
 11946          releaseHistoryTextRemoved = new List<string>();
 11947          releaseHistoryTextInternal = new List<string>();
 11948        }
 11949      }
 11950    }
 11951
 11952    public static bool Toggle(bool value) {
 11953      var toggle = new GUIStyle("Toggle") { margin = new RectOffset(), padding = new RectOffset() };
 11954
 11955      return EditorGUILayout.Toggle(value, toggle, GUILayout.Width(15));
 11956    }
 11957
 11958    private static string BuildPath(params string[] parts) {
 11959      var basePath = "";
 11960
 11961      foreach (var path in parts) {
 11962        basePath = Path.Combine(basePath, path);
 11963      }
 11964
 11965      return basePath.Replace(Application.dataPath, Path.GetFileName(Application.dataPath));
 11966    }
 11967  }
 11968#endif
 11969}
 11970
 11971#endregion
 11972
 11973
 11974#region Assets/Photon/Fusion/Editor/FusionInstaller.cs
 11975
 11976namespace Fusion.Editor {
 11977#if !FUSION_DEV
 11978  using System;
 11979  using System.IO;
 11980  using UnityEditor;
 11981  using UnityEditor.Build;
 11982  using UnityEditor.PackageManager;
 11983  using UnityEngine;
 11984
 11985  [InitializeOnLoad]
 11986  internal class FusionInstaller {
 11987    const string DEFINE_VERSION = "FUSION2";
 11988    const string DEFINE = "FUSION_WEAVER";
 11989    const string PACKAGE_TO_SEARCH = "nuget.mono-cecil";
 11990    const string PACKAGE_TO_INSTALL = "com.unity.nuget.mono-cecil@1.10.2";
 11991    const string PACKAGES_DIR = "Packages";
 11992    const string MANIFEST_FILE = "manifest.json";
 11993
 11994    static FusionInstaller() {
 11995      var defines = GetCurrentDefines();
 11996
 11997      // Check for Defines
 11998      // change based on https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2249
 11999      if (defines.Contains(DEFINE) && defines.Contains(DEFINE_VERSION)) {
 12000        return;
 12001      }
 12002
 12003      if (!PlayerSettings.runInBackground) {
 12004        FusionEditorLog.LogInstaller($"Setting {nameof(PlayerSettings)}.{nameof(PlayerSettings.runInBackground)} to true
 12005        PlayerSettings.runInBackground = true;
 12006      }
 12007
 12008      var manifest = Path.Combine(Path.GetDirectoryName(Application.dataPath) ?? string.Empty, PACKAGES_DIR, MANIFEST_FI
 12009
 12010      if (File.ReadAllText(manifest).Contains(PACKAGE_TO_SEARCH)) {
 12011        FusionEditorLog.LogInstaller($"Setting '{DEFINE}' & '{DEFINE_VERSION}' Define");
 12012
 12013        // append defines
 12014        if (defines.Contains(DEFINE) == false) { defines = $"{defines};{DEFINE}"; }
 12015
 12016        if (defines.Contains(DEFINE_VERSION) == false) { defines = $"{defines};{DEFINE_VERSION}"; }
 12017
 12018        SetCurrentDefines(defines);
 12019      } else {
 12020        FusionEditorLog.LogInstaller($"Installing '{PACKAGE_TO_INSTALL}' package");
 12021        Client.Add(PACKAGE_TO_INSTALL);
 12022      }
 12023    }
 12024
 12025    private static string GetCurrentDefines() {
 12026#if UNITY_SERVER
 12027      var defines = PlayerSettings.GetScriptingDefineSymbols(UnityEditor.Build.NamedBuildTarget.Server);
 12028#else
 12029      var group   = BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget);
 12030      var defines = PlayerSettings.GetScriptingDefineSymbols(NamedBuildTarget.FromBuildTargetGroup(group));
 12031#endif
 12032
 12033      return defines;
 12034    }
 12035
 12036    private static void SetCurrentDefines(string defines) {
 12037#if UNITY_SERVER
 12038      PlayerSettings.SetScriptingDefineSymbols(UnityEditor.Build.NamedBuildTarget.Server, defines);
 12039#else
 12040      var group = BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget);
 12041      PlayerSettings.SetScriptingDefineSymbols(NamedBuildTarget.FromBuildTargetGroup(group), defines);
 12042#endif
 12043    }
 12044  }
 12045#endif
 12046}
 12047
 12048#endregion
 12049
 12050
 12051#region Assets/Photon/Fusion/Editor/FusionSceneSetupAssistants.cs
 12052
 12053namespace Fusion.Editor {
 12054  using UnityEditor;
 12055
 12056  using UnityEngine;
 12057  using UnityEngine.SceneManagement;
 12058  using System.Collections.Generic;
 12059
 12060  public static class FusionSceneSetupAssistants {
 12061
 12062    [MenuItem("Tools/Fusion/Scene/Setup Networking in the Scene", false, FusionAssistants.PRIORITY_LOW + 1)]
 12063    [MenuItem("GameObject/Fusion/Scene/Setup Networking in the Scene", false, FusionAssistants.PRIORITY + 1)]
 12064    public static void AddNetworkingToScene() {
 12065      (FusionBootstrap nds, NetworkRunner nr) n = AddNetworkStartup();
 12066      n.nr.gameObject.EnsureComponentExists<NetworkEvents>();
 12067
 12068      // Get scene and mark scene as dirty.
 12069      DirtyAndSaveScene(n.nds.gameObject.scene);
 12070    }
 12071
 12072    public static (FusionBootstrap, NetworkRunner) AddNetworkStartup() {
 12073      // Restrict to single AudioListener to disallow multiple active in shared instance mode (preventing log spam)
 12074      HandleAudioListeners();
 12075
 12076      // Restrict lights to single active instances node to Lights
 12077      HandleLights();
 12078
 12079      // Add NetworkDebugRunner if missing
 12080      var nds = FusionAssistants.EnsureExistsInScene<FusionBootstrap>("Prototype Network Start");
 12081
 12082      NetworkRunner nr = nds.RunnerPrefab == null ? null : nds.RunnerPrefab.TryGetComponent<NetworkRunner>(out var found
 12083      // Add NetworkRunner to scene if the DebugStart doesn't have one as a prefab set already.
 12084      if (nr == null) {
 12085
 12086        // Add NetworkRunner to scene if NetworkDebugStart doesn't have one set as a prefab already.
 12087        nr = FusionAssistants.EnsureExistsInScene<NetworkRunner>("Prototype Runner");
 12088
 12089        nds.RunnerPrefab = nr;
 12090        // The runner go is also our fallback spawn point... so raise it into the air a bit
 12091        nr.transform.position = new Vector3(0, 3, 0);
 12092      }
 12093
 12094      return (nds, nr);
 12095    }
 12096
 12097    [MenuItem("Tools/Fusion/Scene/Add Current Scene To Build Settings", false, FusionAssistants.PRIORITY_LOW)]
 12098    [MenuItem("GameObject/Fusion/Scene/Add Current Scene To Build Settings", false, FusionAssistants.PRIORITY)]
 12099    public static void AddCurrentSceneToSettings() { DirtyAndSaveScene(SceneManager.GetActiveScene()); }
 12100    public static void DirtyAndSaveScene(Scene scene) {
 12101
 12102      UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(scene);
 12103      var scenename = scene.path;
 12104
 12105      // Give chance to save - required in order to build out. If users cancel will only be able to run in the editor.
 12106      if (scenename == "") {
 12107        UnityEditor.SceneManagement.EditorSceneManager.SaveModifiedScenesIfUserWantsTo(new Scene[] { scene });
 12108        scenename = scene.path;
 12109      }
 12110
 12111      // Add scene to Build and Fusion settings
 12112      if (scenename != "") {
 12113        scene.AddSceneToBuildSettings();
 12114      }
 12115    }
 12116
 12117    [MenuItem("Tools/Fusion/Scene/Setup Multi-Peer AudioListener Handling", false, FusionAssistants.PRIORITY_LOW + 1)]
 12118    [MenuItem("GameObject/Fusion/Scene/Setup Multi-Peer AudioListener Handling", false, FusionAssistants.PRIORITY + 1)]
 12119    public static void HandleAudioListeners() {
 12120      int count = 0;
 12121      foreach (var listener in Object.FindObjectsByType<AudioListener>(FindObjectsInactive.Exclude, FindObjectsSortMode.
 12122        count++;
 12123        listener.EnsureComponentHasVisibilityNode();
 12124      }
 12125      Debug.Log($"{count} {nameof(AudioListener)}(s) found and given a {nameof(RunnerVisibilityLink)} component.");
 12126    }
 12127
 12128    [MenuItem("Tools/Fusion/Scene/Setup Multi-Peer Lights Handling", false, FusionAssistants.PRIORITY_LOW + 1)]
 12129    [MenuItem("GameObject/Fusion/Scene/Setup Multi-Peer Lights Handling", false, FusionAssistants.PRIORITY + 1)]
 12130    public static void HandleLights() {
 12131      int count = 0;
 12132      foreach (var listener in Object.FindObjectsByType<Light>(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) {
 12133        count++;
 12134        listener.EnsureComponentHasVisibilityNode();
 12135      }
 12136      Debug.Log($"{count} {nameof(Light)}(s) found and given a {nameof(RunnerVisibilityLink)} component.");
 12137    }
 12138
 12139    public static void AddSceneToBuildSettings(this Scene scene) {
 12140      var buildScenes = EditorBuildSettings.scenes;
 12141      bool isInBuildScenes = false;
 12142      foreach (var bs in buildScenes) {
 12143        if (bs.path == scene.path) {
 12144          isInBuildScenes = true;
 12145          break;
 12146        }
 12147      }
 12148      if (isInBuildScenes == false) {
 12149        var buildList = new List<EditorBuildSettingsScene>();
 12150        buildList.Add(new EditorBuildSettingsScene(scene.path, true));
 12151        buildList.AddRange(buildScenes);
 12152        Debug.Log($"Added '{scene.path}' as first entry in Build Settings.");
 12153        EditorBuildSettings.scenes = buildList.ToArray();
 12154      }
 12155    }
 12156  }
 12157}
 12158
 12159
 12160#endregion
 12161
 12162
 12163#region Assets/Photon/Fusion/Editor/FusionUnitySurrogateBaseWrapper.cs
 12164
 12165namespace Fusion.Editor {
 12166  using System;
 12167  using Internal;
 12168  using UnityEditor;
 12169  using UnityEngine;
 12170
 12171  internal class FusionUnitySurrogateBaseWrapper : ScriptableObject {
 12172    [SerializeReference]
 12173    public UnitySurrogateBase Surrogate;
 12174    [NonSerialized]
 12175    public SerializedProperty SurrogateProperty;
 12176    [NonSerialized]
 12177    public Type SurrogateType;
 12178  }
 12179}
 12180
 12181#endregion
 12182
 12183
 12184#region Assets/Photon/Fusion/Editor/ILWeaverUtils.cs
 12185
 12186namespace Fusion.Editor {
 12187  using UnityEditor;
 12188  using UnityEditor.Compilation;
 12189
 12190  [InitializeOnLoad]
 12191  public static class ILWeaverUtils {
 12192    [MenuItem("Tools/Fusion/Run Weaver")]
 12193    public static void RunWeaver() {
 12194
 12195      CompilationPipeline.RequestScriptCompilation(
 12196#if UNITY_2021_1_OR_NEWER
 12197        RequestScriptCompilationOptions.CleanBuildCache
 12198#endif
 12199      );
 12200    }
 12201  }
 12202}
 12203
 12204#endregion
 12205
 12206
 12207#region Assets/Photon/Fusion/Editor/NetworkBehaviourEditor.cs
 12208
 12209namespace Fusion.Editor {
 12210  using System;
 12211  using System.Collections.Generic;
 12212  using System.Linq;
 12213  using System.Reflection;
 12214  using UnityEditor;
 12215  using UnityEngine;
 12216
 12217  [CustomEditor(typeof(NetworkBehaviour), true)]
 12218  [CanEditMultipleObjects]
 12219  public class NetworkBehaviourEditor : BehaviourEditor {
 12220
 12221    internal const string NETOBJ_REQUIRED_WARN_TEXT = "This <b>" + nameof(NetworkBehaviour) + "</b> requires a <b>" + na
 12222
 12223    IEnumerable<NetworkBehaviour> ValidTargets => targets
 12224      .Cast<NetworkBehaviour>()
 12225      .Where(x => x.Object && x.Object.IsValid && x.Object.gameObject.activeInHierarchy);
 12226
 12227    [NonSerialized]
 12228    int[] _buffer = Array.Empty<int>();
 12229
 12230
 12231    public override void OnInspectorGUI() {
 12232      base.PrepareOnInspectorGUI();
 12233
 12234      bool hasBeenApplied = false;
 12235#if !FUSION_DISABLE_NBEDITOR_PRESERVE_BACKING_FIELDS
 12236      // serialize unchanged serialized state into zero-initialized memory;
 12237      // this makes sure defaults are preserved
 12238      TransferBackingFields(backingFieldsToState: true);
 12239#endif
 12240      try {
 12241
 12242        // after the original values have been saved, they can be overwritten with
 12243        // whatever is in the state
 12244        foreach (var target in ValidTargets) {
 12245          target.CopyStateToBackingFields();
 12246        }
 12247
 12248        // move C# fields to SerializedObject
 12249        serializedObject.UpdateIfRequiredOrScript();
 12250
 12251        EditorGUI.BeginChangeCheck();
 12252
 12253        base.DrawDefaultInspector();
 12254
 12255        if (EditorGUI.EndChangeCheck()) {
 12256          // serialized properties -> C# fields
 12257          serializedObject.ApplyModifiedProperties();
 12258          hasBeenApplied = true;
 12259
 12260          // C# fields -> state
 12261          foreach (var target in ValidTargets) {
 12262            if (target.Object.HasStateAuthority) {
 12263              target.CopyBackingFieldsToState(false);
 12264            }
 12265          }
 12266
 12267        }
 12268      } finally {
 12269#if !FUSION_DISABLE_NBEDITOR_PRESERVE_BACKING_FIELDS
 12270        // now restore the default values
 12271        TransferBackingFields(backingFieldsToState: false);
 12272        serializedObject.Update();
 12273        if (hasBeenApplied) {
 12274          serializedObject.ApplyModifiedProperties();
 12275        }
 12276      }
 12277#endif
 12278
 12279      DrawNetworkObjectCheck();
 12280      DrawEditorButtons();
 12281    }
 12282
 12283    unsafe bool TransferBackingFields(bool backingFieldsToState) {
 12284
 12285      if (Allocator.REPLICATE_WORD_SIZE == sizeof(int)) {
 12286        int offset = 0;
 12287        bool hadChanges = false;
 12288
 12289        int requiredSize = ValidTargets.Sum(x => x.WordCount);
 12290        if (backingFieldsToState) {
 12291          if (_buffer.Length >= requiredSize) {
 12292            Array.Clear(_buffer, 0, _buffer.Length);
 12293          } else {
 12294            _buffer = new int[requiredSize];
 12295          }
 12296        } else {
 12297          if (_buffer.Length < requiredSize) {
 12298            throw new InvalidOperationException("Buffer is too small");
 12299          }
 12300        }
 12301
 12302        fixed (int* p = _buffer) {
 12303          foreach (var target in ValidTargets) {
 12304            var ptr = target.Ptr;
 12305
 12306            try {
 12307              target.Ptr = p + offset;
 12308              if (backingFieldsToState) {
 12309                target.CopyBackingFieldsToState(false);
 12310              } else {
 12311                target.CopyStateToBackingFields();
 12312              }
 12313
 12314              if (!hadChanges) {
 12315                if (Native.MemCmp(target.Ptr, ptr, target.WordCount * Allocator.REPLICATE_WORD_SIZE) != 0) {
 12316                  hadChanges = true;
 12317                }
 12318              }
 12319
 12320            } finally {
 12321              target.Ptr = ptr;
 12322            }
 12323
 12324            offset += target.WordCount;
 12325          }
 12326        }
 12327
 12328        return hadChanges;
 12329      }
 12330    }
 12331
 12332
 12333    /// <summary>
 12334    /// Checks if GameObject or parent GameObject has a NetworkObject, and draws a warning and buttons for adding one if
 12335    /// </summary>
 12336    /// <param name="nb"></param>
 12337    void DrawNetworkObjectCheck() {
 12338      var targetsWithoutNetworkObjects = targets.Cast<NetworkBehaviour>().Where(x => x.transform.GetParentComponent<Netw
 12339      if (targetsWithoutNetworkObjects.Any()) {
 12340
 12341        using (new FusionEditorGUI.WarningScope(NETOBJ_REQUIRED_WARN_TEXT, 6f)) {
 12342          IEnumerable<GameObject> gameObjects = null;
 12343
 12344          if (GUI.Button(EditorGUILayout.GetControlRect(false, 22), "Add Network Object")) {
 12345            gameObjects = targetsWithoutNetworkObjects.Select(x => x.gameObject).Distinct();
 12346          }
 12347
 12348          if (GUI.Button(EditorGUILayout.GetControlRect(false, 22), "Add Network Object to Root")) {
 12349            gameObjects = targetsWithoutNetworkObjects.Select(x => x.transform.root.gameObject).Distinct();
 12350          }
 12351
 12352          if (gameObjects != null) {
 12353            foreach (var go in gameObjects) {
 12354              Undo.AddComponent<NetworkObject>(go);
 12355            }
 12356          }
 12357        }
 12358      }
 12359    }
 12360  }
 12361}
 12362
 12363
 12364#endregion
 12365
 12366
 12367#region Assets/Photon/Fusion/Editor/NetworkMecanimAnimatorBaker.cs
 12368
 12369namespace Fusion.Editor {
 12370  using System.Linq;
 12371  using UnityEditor;
 12372  using UnityEngine;
 12373
 12374  public static class NetworkMecanimAnimatorBaker {
 12375    [NetworkObjectBakerEditTimeHandler]
 12376    public static bool PostprocessAnimator(NetworkMecanimAnimator animator) {
 12377      bool dirty = false;
 12378      if (animator.Animator == null) {
 12379        animator.Animator = animator.GetComponent<Animator>();
 12380        if (animator.Animator == null) {
 12381          FusionEditorLog.Error($"Cannot bake {animator.name}'s {nameof(NetworkMecanimAnimator)} without an {nameof(Anim
 12382          return false;
 12383        } else {
 12384          dirty = true;
 12385        }
 12386      }
 12387      if (AnimatorControllerTools.GetController(animator.Animator) == null) {
 12388        FusionEditorLog.Error($"Cannot bake {animator.name}'s {nameof(NetworkMecanimAnimator)} without an {nameof(UnityE
 12389        return dirty;
 12390      }
 12391
 12392      AnimatorControllerTools.GetHashesAndNames(animator, null, null, ref animator.TriggerHashes, ref animator.StateHash
 12393
 12394      // this is dictated by the animator controller
 12395      FusionEditorLog.Assert(animator.StateHashes[0] == 0);
 12396      foreach (var hash in animator.StateHashes.Skip(1)) {
 12397        if (hash >= 0 && hash < animator.StateHashes.Length) {
 12398          FusionEditorLog.Error($"State hash {hash} is out of range for {animator.name}");
 12399        }
 12400      }
 12401
 12402      FusionEditorLog.Assert(animator.TriggerHashes[0] == 0);
 12403      foreach (var hash in animator.TriggerHashes.Skip(1)) {
 12404        if (hash >= 0 && hash < animator.TriggerHashes.Length) {
 12405          FusionEditorLog.Error($"Trigger hash {hash} is out of range for {animator.name}");
 12406        }
 12407      }
 12408
 12409      int wordCount = AnimatorControllerTools.GetWordCount(animator);
 12410      if (animator.TotalWords != wordCount) {
 12411        animator.TotalWords = wordCount;
 12412        EditorUtility.SetDirty(animator);
 12413        return true;
 12414      }
 12415
 12416      return dirty;
 12417    }
 12418  }
 12419}
 12420
 12421
 12422#endregion
 12423
 12424
 12425#region Assets/Photon/Fusion/Editor/NetworkObjectBakerEditTime.cs
 12426
 12427namespace Fusion.Editor {
 12428  using System;
 12429  using System.Collections.Generic;
 12430  using System.Linq;
 12431  using System.Reflection;
 12432  using UnityEditor;
 12433  using UnityEngine;
 12434
 12435  public class NetworkObjectBakerEditTime : NetworkObjectBaker {
 12436    private Dictionary<Type, int?> _executionOrderCache = new ();
 12437    private ILookup<Type, Delegate> _bakeHandlers;
 12438
 12439    public NetworkObjectBakerEditTime() {
 12440      _bakeHandlers = TypeCache.GetMethodsWithAttribute<NetworkObjectBakerEditTimeHandlerAttribute>()
 12441        .Select(m => {
 12442          var order = m.GetCustomAttribute<NetworkObjectBakerEditTimeHandlerAttribute>().Order;
 12443
 12444          var parameters = m.GetParameters();
 12445          Assert.Check(parameters.Length == 1);
 12446
 12447          var parameterType = parameters[0].ParameterType;
 12448          Assert.Check(parameterType == typeof(NetworkBehaviour) || parameterType.IsSubclassOf(typeof(NetworkBehaviour))
 12449
 12450          var handler = Delegate.CreateDelegate(typeof(Func<,>).MakeGenericType(parameterType, typeof(bool)), m, true);
 12451          return (parameterType, order, handler);
 12452        })
 12453        .OrderBy(t => t.order)
 12454        .ToLookup(t => t.parameterType, (t) => t.handler);
 12455    }
 12456
 12457    protected override bool TryGetExecutionOrder(MonoBehaviour obj, out int order) {
 12458      // is there a cached value?
 12459      if (_executionOrderCache.TryGetValue(obj.GetType(), out var orderNullable)) {
 12460        order = orderNullable ?? default;
 12461        return orderNullable != null;
 12462      }
 12463
 12464      var monoScript = UnityEditor.MonoScript.FromMonoBehaviour(obj);
 12465      if (monoScript) {
 12466        orderNullable = UnityEditor.MonoImporter.GetExecutionOrder(monoScript);
 12467      } else {
 12468        orderNullable = null;
 12469      }
 12470
 12471      _executionOrderCache.Add(obj.GetType(), orderNullable);
 12472      order = orderNullable ?? default;
 12473      return orderNullable != null;
 12474    }
 12475
 12476    protected override void SetDirty(MonoBehaviour obj) {
 12477      EditorUtility.SetDirty(obj);
 12478    }
 12479
 12480    protected override uint GetSortKey(NetworkObject obj) {
 12481      var  globalId = GlobalObjectId.GetGlobalObjectIdSlow(obj);
 12482      int hash     = 0;
 12483
 12484      hash = HashCodeUtilities.GetHashCodeDeterministic(globalId.identifierType, hash);
 12485      hash = HashCodeUtilities.GetHashCodeDeterministic(globalId.assetGUID, hash);
 12486      hash = HashCodeUtilities.GetHashCodeDeterministic(globalId.targetObjectId, hash);
 12487      hash = HashCodeUtilities.GetHashCodeDeterministic(globalId.targetPrefabId, hash);
 12488
 12489      return (uint)hash;
 12490    }
 12491
 12492    protected override bool PostprocessBehaviour(SimulationBehaviour behaviour) {
 12493      for (var type = behaviour.GetType(); type != typeof(SimulationBehaviour) && type != typeof(NetworkBehaviour); type
 12494        foreach (var handler in _bakeHandlers[type]) {
 12495          if ((bool)handler.DynamicInvoke(behaviour)) {
 12496            return true;
 12497          }
 12498        }
 12499      }
 12500
 12501      return false;
 12502    }
 12503  }
 12504}
 12505
 12506
 12507#endregion
 12508
 12509
 12510#region Assets/Photon/Fusion/Editor/NetworkObjectBakerEditTimeHandlerAttribute.cs
 12511
 12512namespace Fusion.Editor {
 12513  using System;
 12514
 12515  [AttributeUsage(AttributeTargets.Method)]
 12516  public class NetworkObjectBakerEditTimeHandlerAttribute : Attribute {
 12517    public int Order { get; set; }
 12518  }
 12519}
 12520
 12521#endregion
 12522
 12523
 12524#region Assets/Photon/Fusion/Editor/NetworkObjectEditor.cs
 12525
 12526namespace Fusion.Editor {
 12527  using System;
 12528  using System.Collections.Generic;
 12529  using System.Linq;
 12530  using System.Reflection;
 12531  using UnityEditor;
 12532  using UnityEngine;
 12533#if UNITY_2021_2_OR_NEWER
 12534  using UnityEditor.SceneManagement;
 12535
 12536#else
 12537  using UnityEditor.Experimental.SceneManagement;
 12538#endif
 12539
 12540  [CustomEditor(typeof(NetworkObject), true)]
 12541  [InitializeOnLoad]
 12542  [CanEditMultipleObjects]
 12543  public unsafe class NetworkObjectEditor : BehaviourEditor {
 12544    private bool _runtimeInfoFoldout;
 12545
 12546    private static PropertyInfo _isSpawnable   = typeof(NetworkObject).GetPropertyOrThrow(nameof(NetworkObject.IsSpawnab
 12547    private static FieldInfo    _networkTypeId = typeof(NetworkObject).GetFieldOrThrow(nameof(NetworkObject.NetworkTypeI
 12548    private static PropertyInfo _networkId     = typeof(NetworkObject).GetPropertyOrThrow<NetworkId>(nameof(NetworkObjec
 12549    private static FieldInfo    _nestingRoot   = typeof(NetworkObjectHeader).GetFieldOrThrow(nameof(NetworkObjectHeader.
 12550    private static FieldInfo    _nestingKey    = typeof(NetworkObjectHeader).GetFieldOrThrow(nameof(NetworkObjectHeader.
 12551    private static PropertyInfo _InputAuthority   = typeof(NetworkObject).GetPropertyOrThrow(nameof(NetworkObject.InputA
 12552    private static PropertyInfo _StateAuthority   = typeof(NetworkObject).GetPropertyOrThrow(nameof(NetworkObject.StateA
 12553    private static PropertyInfo _HasInputAuthority = typeof(NetworkObject).GetPropertyOrThrow(nameof(NetworkObject.HasIn
 12554    private static PropertyInfo _HasStateAuthority = typeof(NetworkObject).GetPropertyOrThrow(nameof(NetworkObject.HasSt
 12555
 12556    static string GetLoadInfoString(NetworkObjectGuid guid) {
 12557      if (NetworkProjectConfigUtilities.TryGetGlobalPrefabSource(guid, out INetworkPrefabSource prefabSource)) {
 12558        return prefabSource.Description;
 12559      }
 12560
 12561      return "Null";
 12562    }
 12563
 12564    public override void OnInspectorGUI() {
 12565      FusionEditorGUI.InjectScriptHeaderDrawer(serializedObject);
 12566      FusionEditorGUI.ScriptPropertyField(serializedObject);
 12567
 12568      // these properties' isExpanded are going to be used for foldouts; that's the easiest
 12569      // way to get quasi-persistent foldouts
 12570
 12571      var flagsProperty = serializedObject.FindPropertyOrThrow(nameof(NetworkObject.Flags));
 12572      var obj           = (NetworkObject)base.target;
 12573      var netObjType    = typeof(NetworkObject);
 12574
 12575      if (targets.Length == 1) {
 12576        if (AssetDatabase.IsMainAsset(obj.gameObject) || PrefabStageUtility.GetPrefabStage(obj.gameObject)?.prefabConten
 12577          Debug.Assert(!AssetDatabaseUtils.IsSceneObject(obj.gameObject));
 12578
 12579          if (!obj.Flags.IsVersionCurrent()) {
 12580            using (new FusionEditorGUI.WarningScope("Prefab needs to be re-imported.")) {
 12581              if (GUILayout.Button("Reimport")) {
 12582                string assetPath = PrefabStageUtility.GetPrefabStage(obj.gameObject)?.assetPath ?? AssetDatabase.GetAsse
 12583                Debug.Assert(!string.IsNullOrEmpty(assetPath));
 12584                AssetDatabase.ImportAsset(assetPath);
 12585              }
 12586            }
 12587          } else {
 12588            EditorGUILayout.Space();
 12589            EditorGUILayout.LabelField("Prefab Settings", EditorStyles.boldLabel);
 12590
 12591            // Is Spawnable
 12592            {
 12593              EditorGUI.BeginChangeCheck();
 12594
 12595              bool spawnable = EditorGUI.Toggle(FusionEditorGUI.LayoutHelpPrefix(this, _isSpawnable), _isSpawnable.Name,
 12596              if (EditorGUI.EndChangeCheck()) {
 12597                var value = obj.Flags.SetIgnored(!spawnable);
 12598                serializedObject.FindProperty(nameof(NetworkObject.Flags)).intValue = (int)value;
 12599                serializedObject.ApplyModifiedProperties();
 12600              }
 12601
 12602#if FUSION_DEV
 12603              var prefabGuid = GetPrefabGuid(obj);
 12604              FusionEditorGUI.LayoutSelectableLabel(new GUIContent($"Guid"), prefabGuid.ToUnityGuidString());
 12605#endif
 12606
 12607              string loadInfo = "---";
 12608              if (spawnable) {
 12609                string assetPath = PrefabStageUtility.GetPrefabStage(obj.gameObject)?.assetPath ?? AssetDatabase.GetAsse
 12610                if (!string.IsNullOrEmpty(assetPath)) {
 12611                  var guid = AssetDatabase.AssetPathToGUID(assetPath);
 12612                  loadInfo = GetLoadInfoString(NetworkObjectGuid.Parse(guid));
 12613                }
 12614              }
 12615
 12616              EditorGUILayout.LabelField("Prefab Source", loadInfo);
 12617            }
 12618          }
 12619        } else if (AssetDatabaseUtils.IsSceneObject(obj.gameObject)) {
 12620          if (!obj.Flags.IsVersionCurrent()) {
 12621            if (!EditorApplication.isPlaying) {
 12622              using (new FusionEditorGUI.WarningScope("This object hasn't been baked yet. Save the scene or enter playmo
 12623              }
 12624            }
 12625          }
 12626        }
 12627      }
 12628
 12629
 12630      if (EditorApplication.isPlaying && targets.Length == 1) {
 12631        EditorGUILayout.Space();
 12632        flagsProperty.isExpanded = EditorGUILayout.Foldout(flagsProperty.isExpanded, "Runtime Info");
 12633        if (flagsProperty.isExpanded) {
 12634          using (new FusionEditorGUI.BoxScope(null, 1)) {
 12635            EditorGUI.LabelField(FusionEditorGUI.LayoutHelpPrefix(this, _networkTypeId), _networkTypeId.Name, obj.Networ
 12636            EditorGUILayout.Toggle("Is Valid", obj.IsValid);
 12637            if (obj.IsValid) {
 12638              EditorGUI.LabelField(FusionEditorGUI.LayoutHelpPrefix(this, _networkId), _networkId.Name, obj.Id.ToString(
 12639              EditorGUILayout.IntField("Word Count", NetworkObject.GetWordCount(obj));
 12640
 12641
 12642              bool headerIsNull = obj.Meta == null;
 12643              EditorGUI.LabelField(FusionEditorGUI.LayoutHelpPrefix(this, _nestingRoot), _nestingRoot.Name, headerIsNull
 12644              EditorGUI.LabelField(FusionEditorGUI.LayoutHelpPrefix(this, _nestingKey), _nestingKey.Name, headerIsNull ?
 12645
 12646              EditorGUI.LabelField(FusionEditorGUI.LayoutHelpPrefix(this, _InputAuthority), _InputAuthority.Name, obj.In
 12647              EditorGUI.LabelField(FusionEditorGUI.LayoutHelpPrefix(this, _StateAuthority), _StateAuthority.Name, obj.St
 12648
 12649              EditorGUI.Toggle(FusionEditorGUI.LayoutHelpPrefix(this, _HasInputAuthority), _InputAuthority.Name, obj.Has
 12650              EditorGUI.Toggle(FusionEditorGUI.LayoutHelpPrefix(this, _HasStateAuthority), _StateAuthority.Name, obj.Has
 12651
 12652              EditorGUILayout.Toggle("Is Simulated", obj.IsInSimulation);
 12653              EditorGUILayout.Toggle("Is Local PlayerObject", ReferenceEquals(obj.Runner.GetPlayerObject(obj.Runner.Loca
 12654              EditorGUILayout.Toggle("Has Main TRSP", obj.Meta?.HasMainTRSP ?? false);
 12655
 12656              EditorGUILayout.LabelField("Runtime Flags", obj.RuntimeFlags.ToString());
 12657              EditorGUILayout.LabelField("Header Flags", obj.Meta?.Flags.ToString());
 12658
 12659
 12660              if (obj.Runner.IsClient) {
 12661                EditorGUILayout.IntField("Last Received Tick", obj.LastReceiveTick);
 12662              }
 12663            }
 12664          }
 12665        }
 12666      }
 12667
 12668      EditorGUI.BeginChangeCheck();
 12669
 12670      var config    = NetworkProjectConfig.Global;
 12671      var isPlaying = EditorApplication.isPlaying;
 12672
 12673      void DrawToggleFlag(NetworkObjectFlags flag, string name, bool? force = null) {
 12674        var x = (obj.Flags & flag) == flag;
 12675
 12676        var r = EditorGUILayout.Toggle(name, x);
 12677        if (r != x || (force.HasValue && r != force.Value)) {
 12678          if (force.HasValue) {
 12679            r = force.Value;
 12680          }
 12681
 12682          if (r) {
 12683            obj.Flags |= flag;
 12684          } else {
 12685            obj.Flags &= ~flag;
 12686          }
 12687
 12688          EditorUtility.SetDirty(obj);
 12689        }
 12690      }
 12691
 12692      using (new EditorGUI.DisabledScope(isPlaying)) {
 12693        EditorGUILayout.Space();
 12694        EditorGUILayout.LabelField("Shared Mode Settings", EditorStyles.boldLabel);
 12695
 12696        DrawToggleFlag(NetworkObjectFlags.MasterClientObject, "Is Master Client Object");
 12697
 12698        EditorGUI.BeginDisabledGroup((obj.Flags & NetworkObjectFlags.MasterClientObject) == NetworkObjectFlags.MasterCli
 12699        if ((obj.Flags & NetworkObjectFlags.MasterClientObject) == NetworkObjectFlags.MasterClientObject) {
 12700          DrawToggleFlag(NetworkObjectFlags.AllowStateAuthorityOverride, "Allow State Authority Override", false);
 12701        } else {
 12702          DrawToggleFlag(NetworkObjectFlags.AllowStateAuthorityOverride, "Allow State Authority Override");
 12703        }
 12704
 12705        EditorGUI.EndDisabledGroup();
 12706
 12707        EditorGUI.BeginDisabledGroup((obj.Flags & NetworkObjectFlags.AllowStateAuthorityOverride) == default);
 12708
 12709        if ((obj.Flags & NetworkObjectFlags.MasterClientObject) == NetworkObjectFlags.MasterClientObject) {
 12710          DrawToggleFlag(NetworkObjectFlags.DestroyWhenStateAuthorityLeaves, "Destroy When State Authority Leaves", fals
 12711        } else {
 12712          if ((obj.Flags & NetworkObjectFlags.AllowStateAuthorityOverride) == NetworkObjectFlags.AllowStateAuthorityOver
 12713            DrawToggleFlag(NetworkObjectFlags.DestroyWhenStateAuthorityLeaves, "Destroy When State Authority Leaves");
 12714          } else {
 12715            DrawToggleFlag(NetworkObjectFlags.DestroyWhenStateAuthorityLeaves, "Destroy When State Authority Leaves", tr
 12716          }
 12717        }
 12718
 12719        EditorGUI.EndDisabledGroup();
 12720
 12721        //var destroyWhenStateAuthLeaves = serializedObject.FindProperty(nameof(NetworkObject.DestroyWhenStateAuthorityL
 12722        //EditorGUILayout.PropertyField(destroyWhenStateAuthLeaves);
 12723        //
 12724        //var allowStateAuthorityOverride = serializedObject.FindProperty(nameof(NetworkObject.AllowStateAuthorityOverri
 12725        //EditorGUILayout.PropertyField(allowStateAuthorityOverride);
 12726
 12727        EditorGUILayout.Space();
 12728        EditorGUILayout.LabelField("Interest Management Settings", EditorStyles.boldLabel);
 12729
 12730
 12731        var objectInterest = serializedObject.FindProperty(nameof(NetworkObject.ObjectInterest));
 12732        EditorGUILayout.PropertyField(objectInterest);
 12733
 12734        if (objectInterest.intValue == (int)NetworkObject.ObjectInterestModes.AreaOfInterest) {
 12735          //EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(NetworkObject.AreaOfInterestTransform)));
 12736        }
 12737
 12738        //using (new EditorGUI.IndentLevelScope()) {
 12739        //  EditorGUILayout.PropertyField(serializedObject.FindPropertyOrThrow(nameof(NetworkObject.DefaultInterestGroup
 12740        //}
 12741      }
 12742
 12743      if (EditorGUI.EndChangeCheck()) {
 12744        serializedObject.ApplyModifiedProperties();
 12745      }
 12746
 12747      EditorGUILayout.Space();
 12748      EditorGUI.DrawRect(EditorGUILayout.GetControlRect(false, 1), Color.gray);
 12749      EditorGUILayout.Space();
 12750
 12751      EditorGUILayout.LabelField("Baked Data", EditorStyles.boldLabel);
 12752      using (new FusionEditorGUI.BoxScope(null, 1)) {
 12753        using (new EditorGUI.DisabledScope(true)) {
 12754          using (new FusionEditorGUI.ShowMixedValueScope(flagsProperty.hasMultipleDifferentValues)) {
 12755            FusionEditorGUI.LayoutSelectableLabel(EditorGUIUtility.TrTextContent(nameof(obj.Flags)), obj.Flags.ToString(
 12756            FusionEditorGUI.LayoutSelectableLabel(EditorGUIUtility.TrTextContent(nameof(obj.SortKey)), obj.SortKey.ToStr
 12757          }
 12758
 12759          using (new EditorGUI.IndentLevelScope()) {
 12760            EditorGUILayout.PropertyField(serializedObject.FindPropertyOrThrow(nameof(NetworkObject.NestedObjects)));
 12761            EditorGUILayout.PropertyField(serializedObject.FindPropertyOrThrow(nameof(NetworkObject.NetworkedBehaviours)
 12762          }
 12763        }
 12764      }
 12765
 12766      // Runtime buttons
 12767
 12768      if (obj.Runner && obj.Runner.IsRunning) {
 12769
 12770        EditorGUILayout.Space();
 12771        EditorGUI.DrawRect(EditorGUILayout.GetControlRect(false, 1), Color.gray);
 12772        EditorGUILayout.Space();
 12773
 12774        // Input Authority Popup
 12775        using (new EditorGUI.DisabledScope(obj.HasStateAuthority == false)) {
 12776          var elements = GetInputAuthorityPopupContent(obj);
 12777
 12778          var index = EditorGUILayout.Popup(_guiContentInputAuthority, elements.currentIndex, elements.content);
 12779          if (index != elements.currentIndex) {
 12780            obj.AssignInputAuthority(PlayerRef.FromIndex(elements.ids[index]));
 12781          }
 12782        }
 12783
 12784        if (obj.Runner.GameMode == GameMode.Shared) {
 12785          if (GUILayout.Button("Request State Authority")) {
 12786            obj.RequestStateAuthority();
 12787          }
 12788        }
 12789
 12790        if (GUILayout.Button("Despawn")) {
 12791          obj.Runner.Despawn(obj);
 12792        }
 12793      }
 12794    }
 12795
 12796    private static bool Set<T>(UnityEngine.Object host, ref T field, T value, Action<object> setDirty) {
 12797      if (!EqualityComparer<T>.Default.Equals(field, value)) {
 12798        Trace($"Object dirty: {host} ({field} vs {value})");
 12799        setDirty?.Invoke(host);
 12800        field = value;
 12801        return true;
 12802      } else {
 12803        return false;
 12804      }
 12805    }
 12806
 12807    private static bool Set<T>(UnityEngine.Object host, ref T[] field, List<T> value, Action<object> setDirty) {
 12808      var comparer = EqualityComparer<T>.Default;
 12809      if (field == null || field.Length != value.Count || !field.SequenceEqual(value, comparer)) {
 12810        Trace($"Object dirty: {host} ({field} vs {value})");
 12811        setDirty?.Invoke(host);
 12812        field = value.ToArray();
 12813        return true;
 12814      } else {
 12815        return false;
 12816      }
 12817    }
 12818
 12819    [System.Diagnostics.Conditional("FUSION_EDITOR_TRACE")]
 12820    private static void Trace(string msg) {
 12821      Debug.Log($"[Fusion/NetworkObjectEditor] {msg}");
 12822    }
 12823
 12824    public static NetworkObjectGuid GetPrefabGuid(NetworkObject prefab) {
 12825      if (prefab == null) {
 12826        throw new ArgumentNullException(nameof(prefab));
 12827      }
 12828
 12829      if (!AssetDatabase.TryGetGUIDAndLocalFileIdentifier(prefab, out var guidStr, out long _)) {
 12830        throw new ArgumentException($"No guid for {prefab}", nameof(prefab));
 12831      }
 12832
 12833      return NetworkObjectGuid.Parse(guidStr);
 12834    }
 12835
 12836    private static          GUIContent[] _reusableContent;
 12837    private static          int[]        _reusablePlayerIds;
 12838    private static readonly GUIContent   _guiContentEmpty = new GUIContent("");
 12839    private static readonly GUIContent   _guiContentNone  = new GUIContent("None");
 12840    private static readonly GUIContent   _guiContentInputAuthority  = new GUIContent("Input Authority");
 12841
 12842    private static (int[] ids, GUIContent[] content, int currentIndex) GetInputAuthorityPopupContent(NetworkObject obj) 
 12843      int requiredLength = obj.Runner.ActivePlayers.Count() + 2;
 12844      if (_reusableContent == null || requiredLength > _reusableContent.Length) {
 12845        _reusablePlayerIds    = new int[requiredLength];
 12846        _reusablePlayerIds[0] = -1;
 12847        _reusablePlayerIds[1] = 0;
 12848        _reusableContent      = new GUIContent[requiredLength];
 12849        _reusableContent[0]   = _guiContentNone;
 12850        _reusableContent[1]   = _guiContentEmpty;
 12851      }
 12852
 12853      int indexOfCurrentPlayer = 0;
 12854
 12855      // clear
 12856      for (int i = 2; i < _reusableContent.Length; i++) {
 12857        _reusableContent[i] = _guiContentEmpty;
 12858      }
 12859
 12860      int index = 2;
 12861
 12862      foreach (var player in obj.Runner.ActivePlayers) {
 12863        _reusablePlayerIds[index] = player.PlayerId;
 12864        _reusableContent[index]   = new GUIContent($"Player {player.PlayerId}");
 12865        if (player.PlayerId == obj.InputAuthority.PlayerId) {
 12866          indexOfCurrentPlayer = index;
 12867        }
 12868        index++;
 12869      }
 12870      return (_reusablePlayerIds, _reusableContent, indexOfCurrentPlayer);
 12871    }
 12872  }
 12873}
 12874
 12875#endregion
 12876
 12877
 12878#region Assets/Photon/Fusion/Editor/NetworkObjectPostprocessor.cs
 12879
 12880namespace Fusion.Editor {
 12881  using System;
 12882  using System.Collections.Generic;
 12883  using System.Linq;
 12884  using UnityEditor;
 12885  using UnityEditor.Build;
 12886  using UnityEditor.Build.Reporting;
 12887  using UnityEditor.SceneManagement;
 12888  using UnityEngine;
 12889  using UnityEngine.SceneManagement;
 12890
 12891  public class NetworkObjectPostprocessor : AssetPostprocessor {
 12892
 12893    public static event Action<NetworkObjectBakePrefabArgs> OnBakePrefab;
 12894    public static event Action<NetworkObjectBakeSceneArgs> OnBakeScene;
 12895
 12896    static NetworkObjectPostprocessor() {
 12897      EditorSceneManager.sceneSaving += OnSceneSaving;
 12898      EditorApplication.playModeStateChanged += OnPlaymodeChange;
 12899    }
 12900
 12901    static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] m
 12902      FusionEditorLog.TraceImport($"Postprocessing imported assets [{importedAssets.Length}]:\n{string.Join("\n", import
 12903
 12904      bool rebuildPrefabHash = false;
 12905
 12906
 12907      foreach (var path in importedAssets) {
 12908        if (!IsPrefabPath(path)) {
 12909          continue;
 12910        }
 12911
 12912        var go = AssetDatabase.LoadAssetAtPath<GameObject>(path);
 12913        if (!go) {
 12914          continue;
 12915        }
 12916
 12917        var isSpawnable = false;
 12918        var needsBaking = false;
 12919
 12920        var no = go.GetComponent<NetworkObject>();
 12921        if (no) {
 12922          // NO prefab, needs labels adjusted and hash needs to be rebuilt
 12923          rebuildPrefabHash = true;
 12924          needsBaking = true;
 12925          isSpawnable = !no.Flags.IsIgnored();
 12926        }
 12927
 12928        if (AssetDatabaseUtils.SetLabel(go, NetworkProjectConfigImporter.FusionPrefabTag, isSpawnable)) {
 12929          rebuildPrefabHash = true;
 12930          AssetDatabase.ImportAsset(path);
 12931          FusionEditorLog.TraceImport(path, "Labels were dirty");
 12932        } else if (no) {
 12933          FusionEditorLog.TraceImport(path, "Labels up to date");
 12934        }
 12935
 12936        if (needsBaking) {
 12937#if UNITY_2023_1_OR_NEWER || UNITY_2022_3_OR_NEWER
 12938          if (Array.IndexOf(movedAssets, path) >= 0) {
 12939            // attempting to bake a prefab that has been moved would hang the editor
 12940            // https://issuetracker.unity3d.com/issues/editor-freezes-when-prefabutility-dot-loadprefabcontents-is-calle
 12941            continue;
 12942          }
 12943#endif
 12944          FusionEditorLog.TraceImport(path, "Baking");
 12945          BakePrefab(path, out _);
 12946        }
 12947      }
 12948
 12949      foreach (var path in movedAssets) {
 12950        if (!IsPrefabPath(path)) {
 12951          continue;
 12952        }
 12953        if (!AssetDatabaseUtils.HasLabel(path, NetworkProjectConfigImporter.FusionPrefabTag)) {
 12954          continue;
 12955        }
 12956        rebuildPrefabHash = true;
 12957        break;
 12958      }
 12959
 12960      foreach (var path in deletedAssets) {
 12961        if (!IsPrefabPath(path)) {
 12962          continue;
 12963        }
 12964        rebuildPrefabHash = true;
 12965        break;
 12966      }
 12967
 12968      if (rebuildPrefabHash) {
 12969        EditorApplication.delayCall -= NetworkProjectConfigImporter.RefreshNetworkObjectPrefabHash;
 12970        EditorApplication.delayCall += NetworkProjectConfigImporter.RefreshNetworkObjectPrefabHash;
 12971      }
 12972    }
 12973
 12974    static bool IsPrefabPath(string path) {
 12975      return path.EndsWith(".prefab");
 12976    }
 12977
 12978    static bool IsNetworkObjectPrefab(string path, out NetworkObject no) {
 12979      if (!path.EndsWith(".prefab")) {
 12980        // not a prefab
 12981        no = null;
 12982        return false;
 12983      }
 12984
 12985      var go = AssetDatabase.LoadAssetAtPath<GameObject>(path);
 12986      if (!go) {
 12987        no = null;
 12988        return false;
 12989      }
 12990
 12991      no = go.GetComponent<NetworkObject>();
 12992      return no;
 12993    }
 12994
 12995    void OnPostprocessPrefab(GameObject prefab) {
 12996      var no = prefab.GetComponent<NetworkObject>();
 12997
 12998      if (no && no.IsSpawnable) {
 12999        var existing = prefab.GetComponent<NetworkObjectPrefabData>();
 13000        if (existing != null) {
 13001          // this is likely a variant prefab, can't add the next one
 13002          // also, component loses hide flags at this point, so they need to be restored
 13003          // weirdly, this is the only case where altering a component in OnPostprocessPrefab works
 13004          // without causing an import warning
 13005          existing.Guid      = NetworkObjectGuid.Parse(AssetDatabase.AssetPathToGUID(context.assetPath));
 13006          existing.hideFlags = HideFlags.DontSaveInEditor | HideFlags.HideInInspector | HideFlags.NotEditable;
 13007        } else {
 13008          var indirect = prefab.AddComponent<NetworkObjectPrefabData>();
 13009          indirect.Guid      =  NetworkObjectGuid.Parse(AssetDatabase.AssetPathToGUID(context.assetPath));
 13010          indirect.hideFlags |= HideFlags.HideInInspector | HideFlags.NotEditable;
 13011        }
 13012      }
 13013    }
 13014
 13015
 13016    static bool BakePrefab(string prefabPath, out GameObject root) {
 13017
 13018      root = null;
 13019
 13020      var assetGuid = AssetDatabase.AssetPathToGUID(prefabPath);
 13021      if (!NetworkObjectGuid.TryParse(assetGuid, out var guid)) {
 13022        FusionEditorLog.ErrorImport(prefabPath, $"Unable to parse guid: \"{assetGuid}\", not going to bake");
 13023        return false;
 13024      }
 13025
 13026      var stageGo = PrefabUtility.LoadPrefabContents(prefabPath);
 13027      if (!stageGo) {
 13028        FusionEditorLog.ErrorImport(prefabPath, $"Unable to load prefab contents");
 13029        return false;
 13030      }
 13031
 13032      var sw = System.Diagnostics.Stopwatch.StartNew();
 13033
 13034      try {
 13035        bool dirty = false;
 13036        bool baked = false;
 13037
 13038        if (OnBakePrefab != null) {
 13039          var args = new NetworkObjectBakePrefabArgs(_baker, stageGo, prefabPath);
 13040          OnBakePrefab(args);
 13041          if (args.Handled) {
 13042            baked = true;
 13043            dirty = args.IsPrefabDirty;
 13044          }
 13045        }
 13046
 13047        if (!baked) {
 13048          dirty = _baker.Bake(stageGo).HadChanges;
 13049        }
 13050
 13051        FusionEditorLog.TraceImport(prefabPath, $"Baking took {sw.Elapsed}, changed: {dirty}");
 13052
 13053        if (dirty) {
 13054          root = PrefabUtility.SaveAsPrefabAsset(stageGo, prefabPath);
 13055        }
 13056
 13057        return root;
 13058      } finally {
 13059        PrefabUtility.UnloadPrefabContents(stageGo);
 13060      }
 13061    }
 13062
 13063    private static NetworkObjectBaker _baker = new NetworkObjectBakerEditTime();
 13064
 13065    private static void OnPlaymodeChange(PlayModeStateChange change) {
 13066      if (change != PlayModeStateChange.ExitingEditMode) {
 13067        return;
 13068      }
 13069      for (int i = 0; i < EditorSceneManager.sceneCount; ++i) {
 13070        BakeScene(EditorSceneManager.GetSceneAt(i));
 13071      }
 13072    }
 13073
 13074    private static void OnSceneSaving(Scene scene, string path) {
 13075      BakeScene(scene);
 13076    }
 13077
 13078    [MenuItem("Tools/Fusion/Scene/Bake Scene Objects", false, FusionAssistants.PRIORITY_LOW - 1)]
 13079    [MenuItem("GameObject/Fusion/Scene/Bake Scene Objects", false, FusionAssistants.PRIORITY - 1)]
 13080    public static void BakeAllOpenScenes() {
 13081      for (int i = 0; i < SceneManager.sceneCount; ++i) {
 13082        var scene = SceneManager.GetSceneAt(i);
 13083        try {
 13084          BakeScene(scene);
 13085        } catch (Exception ex) {
 13086          Debug.LogError($"Failed to bake scene {scene}: {ex}");
 13087        }
 13088      }
 13089    }
 13090
 13091    public static void BakeScene(Scene scene) {
 13092      var sw = System.Diagnostics.Stopwatch.StartNew();
 13093      try {
 13094
 13095        if (OnBakeScene != null) {
 13096          var args = new NetworkObjectBakeSceneArgs(_baker, scene);
 13097          OnBakeScene(args);
 13098          if (args.Handled) {
 13099            return;
 13100          }
 13101        }
 13102
 13103        foreach (var root in scene.GetRootGameObjects()) {
 13104          _baker.Bake(root);
 13105        }
 13106
 13107      } finally {
 13108        FusionEditorLog.TraceImport(scene.path, $"Baking {scene} took: {sw.Elapsed}");
 13109      }
 13110    }
 13111  }
 13112
 13113  public class NetworkObjectBakePrefabArgs {
 13114    public bool IsPrefabDirty { get; set; }
 13115    public bool Handled { get; set; }
 13116    public GameObject LoadedPrefabRoot { get; }
 13117    public string Path { get; }
 13118    public NetworkObjectBaker Baker { get; }
 13119
 13120    public NetworkObjectBakePrefabArgs(NetworkObjectBaker baker, GameObject loadedPrefabRoot, string path) {
 13121      LoadedPrefabRoot = loadedPrefabRoot;
 13122      Path = path;
 13123      Baker = baker;
 13124    }
 13125  }
 13126
 13127  public class NetworkObjectBakeSceneArgs {
 13128    public bool Handled { get; set; }
 13129    public Scene Scene { get; }
 13130    public NetworkObjectBaker Baker { get; }
 13131
 13132    public NetworkObjectBakeSceneArgs(NetworkObjectBaker baker, Scene scene) {
 13133      Scene = scene;
 13134      Baker = baker;
 13135    }
 13136  }
 13137}
 13138
 13139#endregion
 13140
 13141
 13142#region Assets/Photon/Fusion/Editor/NetworkPrefabSourceFactories.cs
 13143
 13144namespace Fusion.Editor {
 13145  using System;
 13146  using System.Collections.Generic;
 13147  using System.Linq;
 13148  using UnityEditor;
 13149  using UnityEngine;
 13150
 13151  partial interface INetworkAssetSourceFactory {
 13152    INetworkPrefabSource           TryCreatePrefabSource(in NetworkAssetSourceFactoryContext context);
 13153  }
 13154
 13155  public class NetworkAssetSourceFactory {
 13156    private readonly List<INetworkAssetSourceFactory> _factories = TypeCache.GetTypesDerivedFrom<INetworkAssetSourceFact
 13157     .Select(x => (INetworkAssetSourceFactory)Activator.CreateInstance(x))
 13158     .OrderBy(x => x.Order)
 13159     .ToList();
 13160
 13161    public INetworkPrefabSource TryCreatePrefabSource(in NetworkAssetSourceFactoryContext context, bool removeFaultedFac
 13162      for (int i = 0; i < _factories.Count; ++i) {
 13163        var factory = _factories[i];
 13164
 13165        try {
 13166          var source = factory.TryCreatePrefabSource(in context);
 13167          if (source != null) {
 13168            return source;
 13169          }
 13170        } catch (Exception ex) when(removeFaultedFactories) {
 13171          FusionEditorLog.Error($"Prefab source factory {factory.GetType().Name} failed for {context.AssetPath}. " +
 13172            $"This factory will be removed from the list of available factories during this import." +
 13173            $"Reimport of fix the underlying issue: {ex}");
 13174        }
 13175      }
 13176
 13177      return null;
 13178    }
 13179  }
 13180
 13181  partial class NetworkAssetSourceFactoryStatic {
 13182    public INetworkPrefabSource TryCreatePrefabSource(in NetworkAssetSourceFactoryContext context) {
 13183      if (TryCreateInternal<NetworkPrefabSourceStaticLazy, NetworkObject>(context, out var result)) {
 13184        result.AssetGuid = NetworkObjectGuid.Parse(context.AssetGuid);
 13185      };
 13186      return result;
 13187    }
 13188  }
 13189
 13190  partial class NetworkAssetSourceFactoryResource {
 13191    public INetworkPrefabSource TryCreatePrefabSource(in NetworkAssetSourceFactoryContext context) {
 13192      if (TryCreateInternal<NetworkPrefabSourceResource, NetworkObject>(context, out var result)) {
 13193        result.AssetGuid = NetworkObjectGuid.Parse(context.AssetGuid);
 13194      };
 13195      return result;
 13196    }
 13197  }
 13198
 13199#if FUSION_ENABLE_ADDRESSABLES && !FUSION_DISABLE_ADDRESSABLES
 13200  partial class NetworkAssetSourceFactoryAddressable {
 13201    public INetworkPrefabSource TryCreatePrefabSource(in NetworkAssetSourceFactoryContext context) {
 13202      if (TryCreateInternal<NetworkPrefabSourceAddressable, NetworkObject>(context, out var result)) {
 13203        result.AssetGuid = NetworkObjectGuid.Parse(context.AssetGuid);
 13204      };
 13205      return result;
 13206    }
 13207  }
 13208#endif
 13209}
 13210
 13211#endregion
 13212
 13213
 13214#region Assets/Photon/Fusion/Editor/NetworkRunnerEditor.cs
 13215
 13216namespace Fusion.Editor {
 13217  using System;
 13218  using System.Linq;
 13219  using System.Runtime.InteropServices;
 13220  using UnityEditor;
 13221  using UnityEngine;
 13222
 13223  [CustomEditor(typeof(NetworkRunner))]
 13224  public class NetworkRunnerEditor : BehaviourEditor {
 13225
 13226    void Label<T>(string label, T value) {
 13227      EditorGUILayout.LabelField(label, (value != null ? value.ToString() : "null"));
 13228    }
 13229
 13230    public override void OnInspectorGUI() {
 13231      base.OnInspectorGUI();
 13232
 13233      var runner = target as NetworkRunner;
 13234      if (runner && EditorApplication.isPlaying) {
 13235        Label("State", runner.IsRunning ? "Running" : (runner.IsShutdown ? "Shutdown" : "None"));
 13236
 13237        if (runner.IsRunning) {
 13238          Label("Game Mode", runner.GameMode);
 13239          Label("Simulation Mode", runner.Mode);
 13240          Label("Is Player", runner.IsPlayer);
 13241          Label("Local Player", runner.LocalPlayer);
 13242          Label("Has Connection Token?", runner.GetPlayerConnectionToken() != null);
 13243
 13244          var localplayerobj = runner.LocalPlayer.IsRealPlayer ? runner.GetPlayerObject(runner.LocalPlayer) : null;
 13245          EditorGUILayout.ObjectField("Local PlayerObject", localplayerobj, typeof(NetworkObject), true);
 13246
 13247          Label("Is SinglePlayer", runner.IsSinglePlayer);
 13248
 13249          if (runner.TryGetSceneInfo(out var sceneInfo)) {
 13250            Label("Scene Info", sceneInfo);
 13251          } else {
 13252            Label("Scene Info", $"Invalid");
 13253          }
 13254
 13255          var playerCount = runner.ActivePlayers.Count();
 13256          Label("Active Players", playerCount);
 13257
 13258          if (runner.IsServer && playerCount > 0) {
 13259            foreach (var item in runner.ActivePlayers) {
 13260
 13261              // skip local player
 13262              if (runner.LocalPlayer == item) { continue; }
 13263
 13264              Label("Player:PlayerId", item.PlayerId);
 13265              Label("Player:ConnectionType", runner.GetPlayerConnectionType(item));
 13266              Label("Player:UserId", runner.GetPlayerUserId(item));
 13267              Label("Player:RTT", runner.GetPlayerRtt(item));
 13268            }
 13269          }
 13270
 13271          if (runner.IsClient) {
 13272            Label("Is Connected To Server", runner.IsConnectedToServer);
 13273            Label("Current Connection Type", runner.CurrentConnectionType);
 13274          }
 13275        }
 13276
 13277        Label("Is Cloud Ready", runner.IsCloudReady);
 13278
 13279        if (runner.IsCloudReady) {
 13280          Label("Is Shared Mode Master Client", runner.IsSharedModeMasterClient);
 13281          Label("UserId", runner.UserId);
 13282          Label("AuthenticationValues", runner.AuthenticationValues);
 13283        }
 13284
 13285        Label("SessionInfo:IsValid", runner.SessionInfo.IsValid);
 13286
 13287        if (runner.SessionInfo.IsValid) {
 13288          Label("SessionInfo:Name", runner.SessionInfo.Name);
 13289          Label("SessionInfo:IsVisible", runner.SessionInfo.IsVisible);
 13290          Label("SessionInfo:IsOpen", runner.SessionInfo.IsOpen);
 13291          Label("SessionInfo:Region", runner.SessionInfo.Region);
 13292        }
 13293
 13294        Label("LobbyInfo:IsValid", runner.LobbyInfo.IsValid);
 13295
 13296        if (runner.LobbyInfo.IsValid) {
 13297          Label("LobbyInfo:Name", runner.LobbyInfo.Name);
 13298          Label("LobbyInfo:Region", runner.LobbyInfo.Region);
 13299        }
 13300      } else {
 13301        if (runner.TryGetComponent<RunnerEnableVisibility>(out var _) == false) {
 13302          EditorGUILayout.Space(2);
 13303          if (GUI.Button(EditorGUILayout.GetControlRect(), $"Add {nameof(RunnerEnableVisibility)}")) {
 13304            runner.gameObject.AddComponent<RunnerEnableVisibility>();
 13305          }
 13306        }
 13307
 13308      }
 13309    }
 13310  }
 13311}
 13312
 13313#endregion
 13314
 13315
 13316#region Assets/Photon/Fusion/Editor/NetworkSceneDebugStartEditor.cs
 13317
 13318// file deleted
 13319
 13320#endregion
 13321
 13322
 13323#region Assets/Photon/Fusion/Editor/NetworkTRSPEditor.cs
 13324
 13325namespace Fusion.Editor {
 13326
 13327  using UnityEditor;
 13328
 13329  [CustomEditor(typeof(NetworkTRSP), true)]
 13330  public unsafe class NetworkTRSPEditor : NetworkBehaviourEditor {
 13331    public override void OnInspectorGUI() {
 13332      base.OnInspectorGUI();
 13333
 13334      var t = (NetworkTRSP)target;
 13335      using (new EditorGUI.DisabledScope(true)) {
 13336        if (t && t.StateBufferIsValid && t.CanReceiveRenderCallback) {
 13337          var    found = t.Runner.TryFindObject(t.Data.Parent.Object, out var parent);
 13338          EditorGUILayout.LabelField("Parent",  $"'{(found ? parent.name : "Not Available")}' : {t.Data.Parent.Object.To
 13339        }
 13340      }
 13341    }
 13342  }
 13343}
 13344
 13345#endregion
 13346
 13347
 13348#region Assets/Photon/Fusion/Editor/PhotonAppSettingsEditor.cs
 13349
 13350namespace Fusion.Editor {
 13351  using System.Collections;
 13352  using System.Collections.Generic;
 13353  using UnityEngine;
 13354  using UnityEditor;
 13355  using Photon.Realtime;
 13356
 13357  [CustomEditor(typeof(PhotonAppSettings))]
 13358  public class PhotonAppSettingsEditor : Editor {
 13359
 13360    public override void OnInspectorGUI() {
 13361      FusionEditorGUI.InjectScriptHeaderDrawer(serializedObject);
 13362      base.DrawDefaultInspector();
 13363    }
 13364
 13365    [MenuItem("Tools/Fusion/Realtime Settings", priority = 200)]
 13366    public static void PingNetworkProjectConfigAsset() {
 13367      EditorGUIUtility.PingObject(PhotonAppSettings.Global);
 13368      Selection.activeObject = PhotonAppSettings.Global;
 13369    }
 13370  }
 13371
 13372}
 13373
 13374
 13375
 13376#endregion
 13377
 13378
 13379#region Assets/Photon/Fusion/Editor/ReflectionUtils.Partial.cs
 13380
 13381namespace Fusion.Editor {
 13382  using System;
 13383  using System.Collections.Generic;
 13384  using System.Linq;
 13385  using System.Reflection;
 13386  using System.Runtime.CompilerServices;
 13387  using System.Text;
 13388  using UnityEngine;
 13389
 13390  partial class ReflectionUtils {
 13391    public static string GetCSharpConstraints(this Type type) {
 13392      if (type == null) {
 13393        throw new ArgumentNullException(nameof(type));
 13394      }
 13395
 13396      if (!type.IsGenericTypeDefinition) {
 13397        return "";
 13398      }
 13399
 13400      var result = new StringBuilder();
 13401
 13402      foreach (var genericArg in type.GetGenericArguments()) {
 13403        var constraints = new List<string>();
 13404
 13405        var attribs = genericArg.GenericParameterAttributes;
 13406
 13407        if (attribs.HasFlag(GenericParameterAttributes.NotNullableValueTypeConstraint)) {
 13408          if (genericArg.GetCustomAttributes().Any(x => x.GetType().FullName == "System.Runtime.CompilerServices.IsUnman
 13409            constraints.Add("unmanaged");
 13410          } else {
 13411            constraints.Add("struct");
 13412          }
 13413        } else if (attribs.HasFlag(GenericParameterAttributes.ReferenceTypeConstraint)) {
 13414          constraints.Add("class");
 13415        } else {
 13416          foreach (var c in genericArg.GetGenericParameterConstraints().Where(x => !x.IsInterface)) {
 13417            constraints.Add(GetCSharpTypeName(c));
 13418          }
 13419        }
 13420
 13421        foreach (var c in genericArg.GetGenericParameterConstraints().Where(x => x.IsInterface)) {
 13422          constraints.Add(GetCSharpTypeName(c));
 13423        }
 13424
 13425        if (attribs.HasFlag(GenericParameterAttributes.DefaultConstructorConstraint) && !attribs.HasFlag(GenericParamete
 13426          constraints.Add("new()");
 13427        }
 13428
 13429        if (constraints.Any()) {
 13430          if (result.Length != 0) {
 13431            result.Append(" ");
 13432          }
 13433
 13434          result.Append($"where {genericArg.Name} : {string.Join(", ", constraints)}");
 13435        }
 13436      }
 13437
 13438      return result.ToString();
 13439    }
 13440
 13441    public static string GetCSharpTypeName(this Type type, string suffix = null, bool includeNamespace = true, bool incl
 13442
 13443      if (shortNameForBuiltIns) {
 13444        if (type == typeof(bool)) {
 13445          return "bool";
 13446        }
 13447        if (type == typeof(byte)) {
 13448          return "byte";
 13449        }
 13450        if (type == typeof(sbyte)) {
 13451          return "sbyte";
 13452        }
 13453        if (type == typeof(short)) {
 13454          return "short";
 13455        }
 13456        if (type == typeof(ushort)) {
 13457          return "ushort";
 13458        }
 13459        if (type == typeof(int)) {
 13460          return "int";
 13461        }
 13462        if (type == typeof(uint)) {
 13463          return "uint";
 13464        }
 13465        if (type == typeof(long)) {
 13466          return "long";
 13467        }
 13468        if (type == typeof(ulong)) {
 13469          return "ulong";
 13470        }
 13471        if (type == typeof(float)) {
 13472          return "float";
 13473        }
 13474        if (type == typeof(double)) {
 13475          return "double";
 13476        }
 13477        if (type == typeof(char)) {
 13478          return "char";
 13479        }
 13480        if (type == typeof(void)) {
 13481          return "void";
 13482        }
 13483        if (type == typeof(string)) {
 13484          return "string";
 13485        }
 13486        if (type == typeof(object)) {
 13487          return "object";
 13488        }
 13489        if (type == typeof(decimal)) {
 13490          return "decimal";
 13491        }
 13492      }
 13493
 13494      string fullName;
 13495
 13496      if (includeNamespace) {
 13497        fullName = type.FullName;
 13498        if (fullName == null) {
 13499          if (type.IsGenericParameter) {
 13500            fullName = type.Name;
 13501          } else {
 13502            fullName = type.Namespace + "." + type.Name;
 13503          }
 13504        }
 13505      } else {
 13506        fullName = type.Name;
 13507      }
 13508
 13509      if (useGenericNames && type.IsConstructedGenericType) {
 13510        type = type.GetGenericTypeDefinition();
 13511      }
 13512
 13513      string result;
 13514      if (type.IsGenericType) {
 13515        var parentType = fullName.Split('`').First();
 13516        if (includeGenerics) {
 13517          var genericArguments = string.Join(", ", type.GetGenericArguments().Select(x => x.GetCSharpTypeName()));
 13518          result = $"{parentType}{suffix ?? ""}<{genericArguments}>";
 13519        } else {
 13520          result = $"{parentType}{suffix ?? ""}";
 13521        }
 13522      } else {
 13523        result = fullName + (suffix ?? "");
 13524      }
 13525
 13526      return result.Replace('+', '.');
 13527    }
 13528
 13529    public static string GetCSharpTypeGenerics(this Type type, bool useGenericNames = false, bool useGenericPlaceholders
 13530      string result;
 13531      if (type.IsGenericType) {
 13532        var genericArguments = string.Join(", ", type.GetGenericArguments().Select(x => useGenericPlaceholders ? "" : x.
 13533        result = $"<{genericArguments}>";
 13534      } else {
 13535        result = "";
 13536      }
 13537
 13538      result = result.Replace('+', '.');
 13539      return result;
 13540    }
 13541
 13542    public static string GetCSharpAttributeDefinition<T>(this MemberInfo type) where T : Attribute {
 13543      var attributeData = type.GetCustomAttributesData().SingleOrDefault(x => x.AttributeType == typeof(T));
 13544      if (attributeData == null) {
 13545        throw new InvalidOperationException($"Attribute {typeof(T).FullName} not found");
 13546      }
 13547
 13548      // need a fix for generic typeofs
 13549      var constructorArgs = attributeData.ConstructorArguments
 13550        .Select(arg => arg.ArgumentType == typeof(Type) ? $"typeof({((Type)arg.Value).GetCSharpTypeName()})" : arg.ToStr
 13551
 13552      // named generic arguments not yet supported
 13553      var namedArgs = attributeData.NamedArguments
 13554        .Select(arg => arg.ToString());
 13555
 13556      return $"[{attributeData.Constructor.DeclaringType!.FullName}({string.Join(", ", constructorArgs.Concat(namedArgs)
 13557    }
 13558
 13559    public static string GetCSharpVisibility(this MemberInfo memberInfo) {
 13560      if (memberInfo is Type type) {
 13561        return GetTypeVisibility(type.Attributes & TypeAttributes.VisibilityMask);
 13562      }
 13563      if (memberInfo is MethodBase method) {
 13564        return GetMethodVisibility(method.Attributes & MethodAttributes.MemberAccessMask);
 13565      }
 13566      if (memberInfo is PropertyInfo propertyInfo) {
 13567        return GetMethodVisibility(propertyInfo.GetMethod.Attributes & MethodAttributes.MemberAccessMask);
 13568      }
 13569      if (memberInfo is FieldInfo field) {
 13570        return GetFieldVisibility(field.Attributes & FieldAttributes.FieldAccessMask);
 13571      }
 13572      throw new ArgumentException("MemberInfo is not a valid type", nameof(memberInfo));
 13573
 13574      string GetFieldVisibility(FieldAttributes visibility) {
 13575        switch (visibility) {
 13576          case FieldAttributes.Public:
 13577            return "public";
 13578          case FieldAttributes.Family:
 13579            return "protected";
 13580          case FieldAttributes.FamANDAssem:
 13581            return "protected internal";
 13582          case FieldAttributes.Assembly:
 13583            return "internal";
 13584          default:
 13585            return "private";
 13586        }
 13587      }
 13588
 13589      string GetMethodVisibility(MethodAttributes visibility) {
 13590        switch (visibility) {
 13591          case MethodAttributes.Public:
 13592            return "public";
 13593          case MethodAttributes.Family:
 13594            return "protected";
 13595          case MethodAttributes.FamANDAssem:
 13596            return "protected internal";
 13597          case MethodAttributes.Assembly:
 13598            return "internal";
 13599          default:
 13600            return "private";
 13601        }
 13602      }
 13603
 13604      string GetTypeVisibility(TypeAttributes visibility) {
 13605        switch (visibility) {
 13606          case TypeAttributes.Public:
 13607          case TypeAttributes.NestedPublic:
 13608            return "public";
 13609          case TypeAttributes.NestedFamily:
 13610            return "protected";
 13611          case TypeAttributes.NestedFamANDAssem:
 13612            return "protected internal";
 13613          case TypeAttributes.NestedAssembly:
 13614            return "internal";
 13615          case TypeAttributes.NestedPrivate:
 13616            return "private";
 13617          default:
 13618            return "";
 13619        }
 13620      }
 13621    }
 13622
 13623    public static bool IsBackingField(this FieldInfo fieldInfo, out string propertyName) {
 13624      if (!fieldInfo.IsDefined(typeof(CompilerGeneratedAttribute))) {
 13625        propertyName = null;
 13626        return false;
 13627      }
 13628
 13629      if (!fieldInfo.IsPrivate) {
 13630        propertyName = null;
 13631        return false;
 13632      }
 13633
 13634      if (!fieldInfo.Name.StartsWith("<") && !fieldInfo.Name.EndsWith(">k__BackingField")) {
 13635        propertyName = null;
 13636        return false;
 13637      }
 13638
 13639      propertyName = fieldInfo.Name.Substring(1, fieldInfo.Name.Length - 17);
 13640      return true;
 13641    }
 13642
 13643    public static bool IsFixedSizeBuffer(this Type type, out Type elementType, out int size) {
 13644      size = default;
 13645      elementType = default;
 13646
 13647      if (!type.IsValueType) {
 13648        return false;
 13649      }
 13650
 13651      if (!type.Name.EndsWith("e__FixedBuffer")) {
 13652        return false;
 13653      }
 13654
 13655      // this is a bit of a guesswork
 13656      if (type.IsDefined(typeof(CompilerGeneratedAttribute)) &&
 13657          type.IsDefined(typeof(UnsafeValueTypeAttribute)) &&
 13658          type.StructLayoutAttribute != null) {
 13659        // get the .size
 13660        size = type.StructLayoutAttribute.Size;
 13661        elementType = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)[0].FieldType;
 13662        return true;
 13663      }
 13664
 13665      return false;
 13666    }
 13667
 13668    public static Type GetDeclaringType(this Type type, Type stopAt) {
 13669      Debug.Assert(type != null);
 13670
 13671      while (type.DeclaringType != null && type.DeclaringType != stopAt) {
 13672        type = type.DeclaringType;
 13673      }
 13674
 13675      if (stopAt != type.DeclaringType) {
 13676        throw new InvalidOperationException($"Type {type} does not have a declaring type {stopAt}");
 13677      }
 13678
 13679      return type;
 13680    }
 13681  }
 13682}
 13683
 13684#endregion
 13685
 13686
 13687#region Assets/Photon/Fusion/Editor/Statistics/FusionStatisticsEditor.cs
 13688
 13689namespace Fusion.Statistics {
 13690  using UnityEngine;
 13691  using UnityEditor;
 13692
 13693  [CustomEditor(typeof(FusionStatistics))]
 13694  public class FusionStatisticsEditor : Editor {
 13695    public override void OnInspectorGUI() {
 13696      FusionStatistics fusionStatistics = (FusionStatistics)target;
 13697
 13698      EditorGUI.BeginChangeCheck();
 13699      DrawDefaultInspector();
 13700
 13701      if (EditorGUI.EndChangeCheck()) {
 13702        fusionStatistics.OnEditorChange();
 13703      }
 13704
 13705      if (GUILayout.Button("Setup Statistics Panel"))
 13706      {
 13707        fusionStatistics.SetupStatisticsPanel();
 13708      }
 13709      if (GUILayout.Button("Destroy Statistics Panel"))
 13710      {
 13711        fusionStatistics.DestroyStatisticsPanel();
 13712      }
 13713    }
 13714  }
 13715}
 13716
 13717#endregion
 13718
 13719
 13720#region Assets/Photon/Fusion/Editor/Utilities/AnimatorControllerTools.cs
 13721
 13722// ---------------------------------------------------------------------------------------------
 13723// <copyright>PhotonNetwork Framework for Unity - Copyright (C) 2020 Exit Games GmbH</copyright>
 13724// <author>developer@exitgames.com</author>
 13725// ---------------------------------------------------------------------------------------------
 13726
 13727namespace Fusion.Editor {
 13728  using System.Collections.Generic;
 13729  using UnityEngine;
 13730
 13731  using UnityEditor.Animations;
 13732  using UnityEditor;
 13733
 13734  internal static class AnimatorControllerTools {
 13735    //// Attach methods to Fusion.Runtime NetworkedAnimator
 13736    //[InitializeOnLoadMethod]
 13737    //public static void RegisterFusionDelegates() {
 13738    //  NetworkedAnimator.GetWordCountDelegate = GetWordCount;
 13739    //}
 13740
 13741    internal static AnimatorController GetController(Animator a) {
 13742
 13743      RuntimeAnimatorController rac = a.runtimeAnimatorController;
 13744      AnimatorOverrideController overrideController = rac as AnimatorOverrideController;
 13745
 13746      /// recurse until no override controller is found
 13747      while (overrideController != null) {
 13748        rac = overrideController.runtimeAnimatorController;
 13749        overrideController = rac as AnimatorOverrideController;
 13750      }
 13751
 13752      return rac as AnimatorController;
 13753    }
 13754
 13755    private static void GetTriggerNames(AnimatorController ctr, List<string> namelist) {
 13756      namelist.Clear();
 13757
 13758      foreach (var p in ctr.parameters)
 13759        if (p.type == AnimatorControllerParameterType.Trigger) {
 13760          if (namelist.Contains(p.name)) {
 13761            Debug.LogWarning("Identical Trigger Name Found.  Check animator on '" + ctr.name + "' for repeated trigger n
 13762          } else
 13763            namelist.Add(p.name);
 13764        }
 13765    }
 13766
 13767    private static void GetTriggerNames(AnimatorController ctr, List<int> hashlist) {
 13768      hashlist.Clear();
 13769
 13770      foreach (var p in ctr.parameters)
 13771        if (p.type == AnimatorControllerParameterType.Trigger) {
 13772          hashlist.Add(Animator.StringToHash(p.name));
 13773        }
 13774    }
 13775
 13776    /// ------------------------------ STATES --------------------------------------
 13777
 13778    private static void GetStatesNames(AnimatorController ctr, List<string> namelist) {
 13779      namelist.Clear();
 13780
 13781      foreach (var l in ctr.layers) {
 13782        var states = l.stateMachine.states;
 13783        ExtractNames(ctr, l.name, states, namelist);
 13784
 13785        var substates = l.stateMachine.stateMachines;
 13786        ExtractSubNames(ctr, l.name, substates, namelist);
 13787      }
 13788    }
 13789
 13790    private static void ExtractSubNames(AnimatorController ctr, string path, ChildAnimatorStateMachine[] substates, List
 13791      foreach (var s in substates) {
 13792        var sm = s.stateMachine;
 13793        var subpath = path + "." + sm.name;
 13794
 13795        ExtractNames(ctr, subpath, s.stateMachine.states, namelist);
 13796        ExtractSubNames(ctr, subpath, s.stateMachine.stateMachines, namelist);
 13797      }
 13798    }
 13799
 13800    private static void ExtractNames(AnimatorController ctr, string path, ChildAnimatorState[] states, List<string> name
 13801      foreach (var st in states) {
 13802        string name = st.state.name;
 13803        string layerName = path + "." + st.state.name;
 13804        if (!namelist.Contains(name)) {
 13805          namelist.Add(name);
 13806        }
 13807        if (namelist.Contains(layerName)) {
 13808          Debug.LogWarning("Identical State Name <i>'" + st.state.name + "'</i> Found.  Check animator on '" + ctr.name 
 13809        } else
 13810          namelist.Add(layerName);
 13811      }
 13812
 13813    }
 13814
 13815    private static void GetStatesNames(AnimatorController ctr, List<int> hashlist) {
 13816      hashlist.Clear();
 13817
 13818      foreach (var l in ctr.layers) {
 13819        var states = l.stateMachine.states;
 13820        ExtractHashes(ctr, l.name, states, hashlist);
 13821
 13822        var substates = l.stateMachine.stateMachines;
 13823        ExtractSubtHashes(ctr, l.name, substates, hashlist);
 13824      }
 13825
 13826    }
 13827
 13828    private static void ExtractSubtHashes(AnimatorController ctr, string path, ChildAnimatorStateMachine[] substates, Li
 13829      foreach (var s in substates) {
 13830        var sm = s.stateMachine;
 13831        var subpath = path + "." + sm.name;
 13832
 13833        ExtractHashes(ctr, subpath, sm.states, hashlist);
 13834        ExtractSubtHashes(ctr, subpath, sm.stateMachines, hashlist);
 13835      }
 13836    }
 13837
 13838    private static void ExtractHashes(AnimatorController ctr, string path, ChildAnimatorState[] states, List<int> hashli
 13839      foreach (var st in states) {
 13840        int hash = Animator.StringToHash(st.state.name);
 13841        string fullname = path + "." + st.state.name;
 13842        int layrhash = Animator.StringToHash(fullname);
 13843        if (!hashlist.Contains(hash)) {
 13844          hashlist.Add(hash);
 13845        }
 13846        if (hashlist.Contains(layrhash)) {
 13847          Debug.LogWarning("Identical State Name <i>'" + st.state.name + "'</i> Found.  Check animator on '" + ctr.name 
 13848        } else
 13849          hashlist.Add(layrhash);
 13850      }
 13851    }
 13852
 13853    //public static void GetTransitionNames(this AnimatorController ctr, List<string> transInfo)
 13854    //{
 13855    //  transInfo.Clear();
 13856
 13857    //  transInfo.Add("0");
 13858
 13859    //  foreach (var l in ctr.layers)
 13860    //  {
 13861    //    foreach (var st in l.stateMachine.states)
 13862    //    {
 13863    //      string sname = l.name + "." + st.state.name;
 13864
 13865    //      foreach (var t in st.state.transitions)
 13866    //      {
 13867    //        string dname = l.name + "." + t.destinationState.name;
 13868    //        string name = (sname + " -> " + dname);
 13869    //        transInfo.Add(name);
 13870    //        //Debug.Log(sname + " -> " + dname + "   " + Animator.StringToHash(sname + " -> " + dname));
 13871    //      }
 13872    //    }
 13873    //  }
 13874
 13875    //}
 13876
 13877
 13878    //public static void GetTransitions(this AnimatorController ctr, List<TransitionInfo> transInfo)
 13879    //{
 13880    //  transInfo.Clear();
 13881
 13882    //  transInfo.Add(new TransitionInfo(0, 0, 0, 0, 0, 0, false));
 13883
 13884    //  int index = 1;
 13885
 13886    //  foreach (var l in ctr.layers)
 13887    //  {
 13888    //    foreach (var st in l.stateMachine.states)
 13889    //    {
 13890    //      string sname = l.name + "." + st.state.name;
 13891    //      int shash = Animator.StringToHash(sname);
 13892
 13893    //      foreach (var t in st.state.transitions)
 13894    //      {
 13895    //        string dname = l.name + "." + t.destinationState.name;
 13896    //        int dhash = Animator.StringToHash(dname);
 13897    //        int hash = Animator.StringToHash(sname + " -> " + dname);
 13898    //        TransitionInfo ti = new TransitionInfo(index, hash, shash, dhash, t.duration, t.offset, t.hasFixedDuration
 13899    //        transInfo.Add(ti);
 13900    //        //Debug.Log(index + " " + sname + " -> " + dname + "   " + Animator.StringToHash(sname + " -> " + dname));
 13901    //        index++;
 13902    //      }
 13903    //    }
 13904    //  }
 13905    //}
 13906
 13907    const double AUTO_REBUILD_RATE = 10f;
 13908    private static List<string> tempNamesList = new List<string>();
 13909    private static List<int> tempHashList = new List<int>();
 13910
 13911    /// <summary>
 13912    /// Re-index all of the State and Trigger names in the current AnimatorController. Never hurts to run this (other th
 13913    /// </summary>
 13914    internal static void GetHashesAndNames(NetworkMecanimAnimator netAnim,
 13915        List<string> sharedTriggNames,
 13916        List<string> sharedStateNames,
 13917        ref int[] sharedTriggIndexes,
 13918        ref int[] sharedStateIndexes
 13919        //ref double lastRebuildTime
 13920        ) {
 13921
 13922      // always get new Animator in case it has changed.
 13923      Animator animator = netAnim.Animator;
 13924      if (animator == null)
 13925        animator = netAnim.GetComponent<Animator>();
 13926
 13927      if (animator == null) {
 13928        return;
 13929      }
 13930      //if (animator && EditorApplication.timeSinceStartup - lastRebuildTime > AUTO_REBUILD_RATE) {
 13931      //  lastRebuildTime = EditorApplication.timeSinceStartup;
 13932
 13933      AnimatorController ac = GetController(animator);
 13934      if (ac != null) {
 13935        if (ac.animationClips == null || ac.animationClips.Length == 0)
 13936          Debug.LogWarning("'" + animator.name + "' has an Animator with no animation clips. Some Animator Controllers r
 13937
 13938        bool haschanged = false;
 13939
 13940        GetTriggerNames(ac, tempHashList);
 13941        tempHashList.Insert(0, 0);
 13942        if (!CompareIntArray(sharedTriggIndexes, tempHashList)) {
 13943          sharedTriggIndexes = tempHashList.ToArray();
 13944          haschanged = true;
 13945        }
 13946
 13947        GetStatesNames(ac, tempHashList);
 13948        tempHashList.Insert(0, 0);
 13949        if (!CompareIntArray(sharedStateIndexes, tempHashList)) {
 13950          sharedStateIndexes = tempHashList.ToArray();
 13951          haschanged = true;
 13952        }
 13953
 13954        if (sharedTriggNames != null) {
 13955          GetTriggerNames(ac, tempNamesList);
 13956          tempNamesList.Insert(0, null);
 13957          if (!CompareNameLists(tempNamesList, sharedTriggNames)) {
 13958            CopyNameList(tempNamesList, sharedTriggNames);
 13959            haschanged = true;
 13960          }
 13961        }
 13962
 13963        if (sharedStateNames != null) {
 13964          GetStatesNames(ac, tempNamesList);
 13965          tempNamesList.Insert(0, null);
 13966          if (!CompareNameLists(tempNamesList, sharedStateNames)) {
 13967            CopyNameList(tempNamesList, sharedStateNames);
 13968            haschanged = true;
 13969          }
 13970        }
 13971
 13972        if (haschanged) {
 13973          Debug.Log(animator.name + " has changed. SyncAnimator indexes updated.");
 13974          EditorUtility.SetDirty(netAnim);
 13975        }
 13976      }
 13977      //}
 13978    }
 13979
 13980    /// <summary>
 13981    /// Returns the <see cref="NetworkMecanimAnimator"/>'s word count, using the animator's animator controller.
 13982    /// </summary>
 13983    internal static int GetWordCount(NetworkMecanimAnimator nma) {
 13984      if (nma.Animator == null) {
 13985        return 0;
 13986      }
 13987
 13988      AnimatorController ac = GetController(nma.Animator);
 13989      return NetworkMecanimAnimator.AnimatorData.GetWordCount(nma.SyncSettings, ac.parameters, new int[ac.parameters.Len
 13990    }
 13991
 13992    private static bool CompareNameLists(List<string> one, List<string> two) {
 13993      if (one.Count != two.Count)
 13994        return false;
 13995
 13996      for (int i = 0; i < one.Count; i++)
 13997        if (one[i] != two[i])
 13998          return false;
 13999
 14000      return true;
 14001    }
 14002
 14003    private static bool CompareIntArray(int[] old, List<int> temp) {
 14004      if (ReferenceEquals(old, null))
 14005        return false;
 14006
 14007      if (old.Length != temp.Count)
 14008        return false;
 14009
 14010      for (int i = 0; i < old.Length; i++)
 14011        if (old[i] != temp[i])
 14012          return false;
 14013
 14014      return true;
 14015    }
 14016
 14017    private static void CopyNameList(List<string> src, List<string> trg) {
 14018      trg.Clear();
 14019      for (int i = 0; i < src.Count; i++)
 14020        trg.Add(src[i]);
 14021    }
 14022
 14023  }
 14024
 14025}
 14026
 14027
 14028
 14029#endregion
 14030
 14031
 14032#region Assets/Photon/Fusion/Editor/Utilities/AssetDatabaseUtils.cs
 14033
 14034namespace Fusion.Editor {
 14035  using System;
 14036  using System.Collections.Generic;
 14037  using System.Linq;
 14038  using System.Text;
 14039  using System.Threading.Tasks;
 14040  using UnityEditor;
 14041#if UNITY_2021_2_OR_NEWER
 14042  using UnityEditor.SceneManagement;
 14043#else
 14044  using UnityEditor.Experimental.SceneManagement;
 14045#endif
 14046
 14047  using UnityEngine;
 14048
 14049  public static partial class AssetDatabaseUtils {
 14050    public static T GetSubAsset<T>(GameObject prefab) where T : ScriptableObject {
 14051
 14052      if (!AssetDatabase.IsMainAsset(prefab)) {
 14053        throw new InvalidOperationException($"Not a main asset: {prefab}");
 14054      }
 14055
 14056      string path = AssetDatabase.GetAssetPath(prefab);
 14057      if (string.IsNullOrEmpty(path)) {
 14058        throw new InvalidOperationException($"Empty path for prefab: {prefab}");
 14059      }
 14060
 14061      var subAssets = AssetDatabase.LoadAllAssetsAtPath(path).OfType<T>().ToList();
 14062      if (subAssets.Count > 1) {
 14063        Debug.LogError($"More than 1 asset of type {typeof(T)} on {path}, clean it up manually");
 14064      }
 14065
 14066      return subAssets.Count == 0 ? null : subAssets[0];
 14067    }
 14068
 14069    public static bool IsSceneObject(GameObject go) {
 14070      return ReferenceEquals(PrefabStageUtility.GetPrefabStage(go), null) && (PrefabUtility.IsPartOfPrefabAsset(go) == f
 14071    }
 14072  }
 14073}
 14074
 14075
 14076#endregion
 14077
 14078
 14079#region Assets/Photon/Fusion/Editor/Utilities/FusionEditorGUI.cs
 14080
 14081namespace Fusion.Editor {
 14082  using System;
 14083  using System.Collections.Generic;
 14084  using System.Linq;
 14085  using System.Text;
 14086  using System.Threading.Tasks;
 14087  using UnityEditor;
 14088  using UnityEngine;
 14089
 14090  public static partial class FusionEditorGUI {
 14091
 14092    public static void LayoutSelectableLabel(GUIContent label, string contents) {
 14093      var rect = EditorGUILayout.GetControlRect();
 14094      rect = EditorGUI.PrefixLabel(rect, label);
 14095      using (new EditorGUI.IndentLevelScope(-EditorGUI.indentLevel)) {
 14096        EditorGUI.SelectableLabel(rect, contents);
 14097      }
 14098    }
 14099
 14100    public static bool DrawDefaultInspector(SerializedObject obj, bool drawScript = true) {
 14101      EditorGUI.BeginChangeCheck();
 14102      obj.UpdateIfRequiredOrScript();
 14103
 14104      // Loop through properties and create one field (including children) for each top level property.
 14105      SerializedProperty property = obj.GetIterator();
 14106      bool expanded = true;
 14107      while (property.NextVisible(expanded)) {
 14108        if ( ScriptPropertyName == property.propertyPath ) {
 14109          if (drawScript) {
 14110            using (new EditorGUI.DisabledScope("m_Script" == property.propertyPath)) {
 14111              EditorGUILayout.PropertyField(property, true);
 14112            }
 14113          }
 14114        } else {
 14115          EditorGUILayout.PropertyField(property, true);
 14116        }
 14117        expanded = false;
 14118      }
 14119
 14120      obj.ApplyModifiedProperties();
 14121      return EditorGUI.EndChangeCheck();
 14122    }
 14123  }
 14124}
 14125
 14126
 14127#endregion
 14128
 14129
 14130#region Assets/Photon/Fusion/Editor/Utilities/FusionEditorGUI.Thumbnail.cs
 14131
 14132namespace Fusion.Editor {
 14133  using System;
 14134  using System.Text;
 14135  using UnityEngine;
 14136
 14137  public static partial class FusionEditorGUI {
 14138
 14139    static readonly int _thumbnailFieldHash = "Thumbnail".GetHashCode();
 14140    static Texture2D _thumbnailBackground;
 14141    static GUIStyle _thumbnailStyle;
 14142
 14143    public static void DrawTypeThumbnail(Rect position, Type type, string prefixToSkip, string tooltip = null) {
 14144      EnsureThumbnailStyles();
 14145
 14146      var acronym = GenerateAcronym(type, prefixToSkip);
 14147      var content = new GUIContent(acronym, tooltip ?? type.FullName);
 14148      int controlID = GUIUtility.GetControlID(_thumbnailFieldHash, FocusType.Passive, position);
 14149
 14150      if (Event.current.type == EventType.Repaint) {
 14151        var originalColor = GUI.backgroundColor;
 14152        try {
 14153          GUI.backgroundColor = GetPersistentColor(type.FullName);
 14154          _thumbnailStyle.fixedWidth = position.width;
 14155          _thumbnailStyle.Draw(position, content, controlID);
 14156        } finally {
 14157          GUI.backgroundColor = originalColor;
 14158        }
 14159      }
 14160    }
 14161
 14162    static Color GetPersistentColor(string str) {
 14163      return GeneratePastelColor(HashCodeUtilities.GetHashDeterministic(str));
 14164    }
 14165
 14166    static Color GeneratePastelColor(int seed) {
 14167      var rng = new System.Random(seed);
 14168      int r = rng.Next(256) + 128;
 14169      int g = rng.Next(256) + 128;
 14170      int b = rng.Next(256) + 128;
 14171
 14172      r = Mathf.Min(r / 2, 255);
 14173      g = Mathf.Min(g / 2, 255);
 14174      b = Mathf.Min(b / 2, 255);
 14175
 14176      var result = new Color32((byte)r, (byte)g, (byte)b, 255);
 14177      return result;
 14178    }
 14179
 14180    static string GenerateAcronym(Type type, string prefixToStrip) {
 14181      StringBuilder acronymBuilder = new StringBuilder();
 14182
 14183      var str = type.Name;
 14184      if (!string.IsNullOrEmpty(prefixToStrip)) {
 14185        if (str.StartsWith(prefixToStrip)) {
 14186          str = str.Substring(prefixToStrip.Length);
 14187        }
 14188      }
 14189
 14190      for (int i = 0; i < str.Length; ++i) {
 14191        var c = str[i];
 14192        if (i != 0 && char.IsLower(c)) {
 14193          continue;
 14194        }
 14195        acronymBuilder.Append(c);
 14196      }
 14197
 14198      return acronymBuilder.ToString();
 14199    }
 14200
 14201    static void EnsureThumbnailStyles() {
 14202      if (_thumbnailBackground != null) {
 14203        return;
 14204      }
 14205
 14206      byte[] data = {
 14207        0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
 14208        0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14,
 14209        0x08, 0x06, 0x00, 0x00, 0x00, 0x8d, 0x89, 0x1d, 0x0d, 0x00, 0x00, 0x00,
 14210        0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00,
 14211        0x00, 0x04, 0x67, 0x41, 0x4d, 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc,
 14212        0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00,
 14213        0x0e, 0xc3, 0x00, 0x00, 0x0e, 0xc3, 0x01, 0xc7, 0x6f, 0xa8, 0x64, 0x00,
 14214        0x00, 0x00, 0xf2, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4f, 0xed, 0x95, 0x31,
 14215        0x0a, 0x83, 0x30, 0x14, 0x86, 0x63, 0x11, 0x74, 0x50, 0x74, 0x71, 0xf1,
 14216        0x34, 0x01, 0x57, 0x6f, 0xe8, 0xe0, 0xd0, 0xa5, 0x07, 0x10, 0x0a, 0xbd,
 14217        0x40, 0x0f, 0xe2, 0xa8, 0x9b, 0xee, 0xf6, 0x7d, 0x69, 0x4a, 0xa5, 0xd2,
 14218        0x2a, 0xa6, 0x4b, 0xa1, 0x1f, 0x04, 0x5e, 0xc2, 0xff, 0xbe, 0x68, 0x90,
 14219        0xa8, 0x5e, 0xd0, 0x69, 0x9a, 0x9e, 0xc3, 0x30, 0x1c, 0xa4, 0x9e, 0x3e,
 14220        0x0d, 0x32, 0x64, 0xa5, 0xd6, 0x32, 0x16, 0xf8, 0x51, 0x14, 0x1d, 0xb3,
 14221        0x2c, 0x1b, 0xab, 0xaa, 0x9a, 0xda, 0xb6, 0x9d, 0xd6, 0x20, 0x43, 0x96,
 14222        0x1e, 0x7a, 0x71, 0xdc, 0x55, 0x02, 0x0b, 0x45, 0x51, 0x0c, 0x82, 0x8d,
 14223        0x6f, 0x87, 0x1e, 0x7a, 0xad, 0xd4, 0xa0, 0xd9, 0x65, 0x8f, 0xec, 0x01,
 14224        0xbd, 0x38, 0x70, 0x29, 0xce, 0x81, 0x47, 0x77, 0x05, 0x87, 0x39, 0x53,
 14225        0x0e, 0x77, 0xcb, 0x99, 0xad, 0x81, 0x03, 0x97, 0x27, 0x8f, 0xc9, 0xdc,
 14226        0xbc, 0xbb, 0x2b, 0x9e, 0xe7, 0xa9, 0x83, 0xad, 0xbf, 0xc6, 0x5f, 0xe8,
 14227        0xce, 0x0f, 0x08, 0xe5, 0x63, 0x1c, 0xfb, 0xbe, 0xb7, 0xd3, 0xfd, 0xe0,
 14228        0xc0, 0x75, 0x08, 0x82, 0xe0, 0xda, 0x34, 0x8d, 0x5d, 0xde, 0x0f, 0x0e,
 14229        0x5c, 0xd4, 0x3a, 0xcf, 0x73, 0xe7, 0xcb, 0x01, 0x07, 0x2e, 0x84, 0x2a,
 14230        0x8e, 0xe3, 0x53, 0x59, 0x96, 0xbb, 0xa4, 0xf4, 0xd0, 0x8b, 0xc3, 0xc8,
 14231        0x2c, 0x3e, 0x0b, 0xec, 0x52, 0xd7, 0xf5, 0xd4, 0x75, 0x9d, 0x8d, 0xbf,
 14232        0x87, 0x0c, 0x59, 0x7a, 0xac, 0xec, 0x79, 0xc1, 0xce, 0xd0, 0x49, 0x92,
 14233        0x5c, 0xb8, 0x35, 0xa4, 0x5e, 0x5c, 0xfb, 0xf3, 0x41, 0x86, 0xac, 0xd4,
 14234        0xb3, 0x5f, 0x80, 0x52, 0x37, 0xfd, 0x56, 0x1b, 0x09, 0x40, 0x56, 0xe4,
 14235        0x85, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60,
 14236        0x82
 14237      };
 14238
 14239      var texture = new Texture2D(2, 2, TextureFormat.ARGB32, false);
 14240      if (!texture.LoadImage(data)) {
 14241        throw new InvalidOperationException();
 14242      }
 14243
 14244      _thumbnailBackground = texture;
 14245
 14246      _thumbnailStyle = new GUIStyle() {
 14247        normal = new GUIStyleState { background = _thumbnailBackground, textColor = Color.white },
 14248        border = new RectOffset(6, 6, 6, 6),
 14249        padding = new RectOffset(2, 1, 1, 1),
 14250        imagePosition = ImagePosition.TextOnly,
 14251        alignment = TextAnchor.MiddleCenter,
 14252        clipping = TextClipping.Clip,
 14253        wordWrap = true,
 14254        stretchWidth = false,
 14255        fontSize = 8,
 14256        fontStyle = FontStyle.Bold,
 14257        fixedWidth = texture.width,
 14258      };
 14259
 14260    }
 14261
 14262  }
 14263}
 14264
 14265
 14266#endregion
 14267
 14268
 14269#region Assets/Photon/Fusion/Editor/Utilities/NetworkProjectConfigUtilities.cs
 14270
 14271namespace Fusion.Editor {
 14272
 14273  using UnityEditor;
 14274  using UnityEngine;
 14275  using UnityEngine.SceneManagement;
 14276  using System.Collections.Generic;
 14277  using Fusion.Photon.Realtime;
 14278  using System.Linq;
 14279  using System.IO;
 14280  using System;
 14281
 14282  /// <summary>
 14283  /// Editor utilities for creating and managing the <see cref="NetworkProjectConfigAsset"/> singleton.
 14284  /// </summary>
 14285  [InitializeOnLoad]
 14286  public static class NetworkProjectConfigUtilities {
 14287
 14288    // Constructor runs on project load, allows for startup check for existence of NPC asset.
 14289    static NetworkProjectConfigUtilities() {
 14290      EditorApplication.playModeStateChanged += (change) => {
 14291        if (change == PlayModeStateChange.EnteredEditMode) {
 14292          NetworkProjectConfig.UnloadGlobal();
 14293        }
 14294      };
 14295    }
 14296
 14297    [MenuItem("Tools/Fusion/Network Project Config", priority = 200)]
 14298    [MenuItem("Assets/Create/Fusion/Network Project Config", priority = 0)]
 14299    static void PingNetworkProjectConfigAsset() {
 14300      FusionGlobalScriptableObjectUtils.EnsureAssetExists<NetworkProjectConfigAsset>();
 14301      NetworkProjectConfigUtilities.PingGlobalConfigAsset(true);
 14302    }
 14303
 14304    [MenuItem("Tools/Fusion/Rebuild Prefab Table", priority = 100)]
 14305    public static void RebuildPrefabTable() {
 14306      foreach (var prefab in AssetDatabase.FindAssets($"t:prefab")
 14307        .Select(AssetDatabase.GUIDToAssetPath)
 14308        .Select(x => (GameObject)AssetDatabase.LoadMainAssetAtPath(x))) {
 14309        if (prefab.TryGetComponent<NetworkObject>(out var networkObject) && !networkObject.Flags.IsIgnored()) {
 14310          AssetDatabaseUtils.SetLabel(prefab, NetworkProjectConfigImporter.FusionPrefabTag, true);
 14311        } else {
 14312          AssetDatabaseUtils.SetLabel(prefab, NetworkProjectConfigImporter.FusionPrefabTag, false);
 14313        }
 14314      }
 14315
 14316      AssetDatabase.Refresh();
 14317      ImportGlobalConfig();
 14318
 14319      Debug.Log("Rebuild Prefab Table done.");
 14320    }
 14321
 14322    public static void PingGlobalConfigAsset(bool select = false) {
 14323      if (NetworkProjectConfigAsset.TryGetGlobal(out var config)) {
 14324        EditorGUIUtility.PingObject(config);
 14325        if (select) {
 14326          Selection.activeObject = config;
 14327        }
 14328      }
 14329    }
 14330
 14331    public static bool TryGetGlobalPrefabSource<T>(NetworkObjectGuid guid, out T source) where T : class, INetworkPrefab
 14332      if (NetworkProjectConfigAsset.TryGetGlobal(out var global)) {
 14333        if (global.Config.PrefabTable.GetSource(guid) is T sourceT) {
 14334          source = sourceT;
 14335          return true;
 14336        }
 14337      }
 14338      source = null;
 14339      return false;
 14340    }
 14341
 14342    public static bool TryGetPrefabId(NetworkObjectGuid guid, out NetworkPrefabId id) {
 14343      id = NetworkProjectConfig.Global.PrefabTable.GetId(guid);
 14344      return id.IsValid;
 14345    }
 14346
 14347    public static bool TryGetPrefabId(string prefabPath, out NetworkPrefabId id) {
 14348      var guidStr = AssetDatabase.AssetPathToGUID(prefabPath);
 14349      if (NetworkObjectGuid.TryParse(guidStr, out var guid)) {
 14350        return TryGetPrefabId(guid, out id);
 14351      }
 14352
 14353      id = default;
 14354      return false;
 14355    }
 14356
 14357    // public static bool TryResolvePrefab(NetworkObjectGuid guid, out NetworkObject prefab) {
 14358    //   if (TryGetPrefabSource(guid, out NetworkPrefabSourceBase source)) {
 14359    //     try {
 14360    //       prefab = NetworkPrefabSourceFactory.ResolveOrThrow(source);
 14361    //       return true;
 14362    //     } catch (Exception ex) {
 14363    //       FusionEditorLog.Trace(ex.ToString());
 14364    //     }
 14365    //   }
 14366    //
 14367    //   prefab = null;
 14368    //   return false;
 14369    // }
 14370
 14371    internal static bool TryGetPrefabEditorInstance(NetworkObjectGuid guid, out NetworkObject prefab) {
 14372      if (!guid.IsValid) {
 14373        prefab = null;
 14374        return false;
 14375      }
 14376
 14377      var path = AssetDatabase.GUIDToAssetPath(guid.ToUnityGuidString());
 14378      if (string.IsNullOrEmpty(path)) {
 14379        prefab = null;
 14380        return false;
 14381      }
 14382
 14383      var gameObject = AssetDatabase.LoadAssetAtPath<GameObject>(path);
 14384      if (!gameObject) {
 14385        prefab = null;
 14386        return false;
 14387      }
 14388
 14389      prefab = gameObject.GetComponent<NetworkObject>();
 14390      return prefab;
 14391    }
 14392
 14393    internal static string GetGlobalConfigPath() {
 14394      return FusionGlobalScriptableObjectUtils.GetGlobalAssetPath<NetworkProjectConfigAsset>();
 14395    }
 14396
 14397    public static bool ImportGlobalConfig() {
 14398      return FusionGlobalScriptableObjectUtils.TryImportGlobal<NetworkProjectConfigAsset>();
 14399    }
 14400
 14401    public static string SaveGlobalConfig() {
 14402      if (NetworkProjectConfigAsset.TryGetGlobal(out var global)) {
 14403        return SaveGlobalConfig(global.Config);
 14404      } else {
 14405        return SaveGlobalConfig(new NetworkProjectConfig());
 14406      }
 14407    }
 14408
 14409    public static string SaveGlobalConfig(NetworkProjectConfig config) {
 14410      FusionGlobalScriptableObjectUtils.EnsureAssetExists<NetworkProjectConfigAsset>();
 14411      string path = GetGlobalConfigPath();
 14412
 14413      var json = EditorJsonUtility.ToJson(config, true);
 14414      string existingJson = File.ReadAllText(path);
 14415
 14416      if (!string.Equals(json, existingJson)) {
 14417        AssetDatabase.MakeEditable(path);
 14418        File.WriteAllText(path, json);
 14419      }
 14420
 14421      AssetDatabase.ImportAsset(path);
 14422      return PathUtils.Normalize(path);
 14423    }
 14424
 14425    private static string[] GetEnabledBuildScenes() {
 14426      var scenes = new List<string>();
 14427
 14428      for (int i = 0; i < EditorBuildSettings.scenes.Length; ++i) {
 14429        var scene = EditorBuildSettings.scenes[i];
 14430        if (scene.enabled && string.IsNullOrEmpty(scene.path) == false) {
 14431          scenes.Add(scene.path);
 14432        }
 14433      }
 14434
 14435      return scenes.ToArray();
 14436    }
 14437  }
 14438}
 14439
 14440
 14441#endregion
 14442
 14443
 14444#region Assets/Photon/Fusion/Editor/Utilities/NetworkRunnerUtilities.cs
 14445
 14446namespace Fusion.Editor {
 14447
 14448  using System.Collections.Generic;
 14449  using UnityEngine;
 14450  using UnityEditor;
 14451
 14452  public static class NetworkRunnerUtilities {
 14453
 14454    static List<NetworkRunner> reusableRunnerList = new List<NetworkRunner>();
 14455
 14456    public static NetworkRunner[] FindActiveRunners() {
 14457      var runners = Object.FindObjectsByType<NetworkRunner>(FindObjectsInactive.Exclude, FindObjectsSortMode.InstanceID)
 14458      reusableRunnerList.Clear();
 14459      for (int i = 0; i < runners.Length; ++i) {
 14460        if (runners[i].IsRunning)
 14461          reusableRunnerList.Add(runners[i]);
 14462      }
 14463      if (reusableRunnerList.Count == runners.Length)
 14464        return runners;
 14465
 14466      return reusableRunnerList.ToArray();
 14467    }
 14468
 14469    public static void FindActiveRunners(List<NetworkRunner> nonalloc) {
 14470      var runners = Object.FindObjectsByType<NetworkRunner>(FindObjectsInactive.Exclude, FindObjectsSortMode.InstanceID)
 14471      nonalloc.Clear();
 14472      for (int i = 0; i < runners.Length; ++i) {
 14473        if (runners[i].IsRunning)
 14474          nonalloc.Add(runners[i]);
 14475      }
 14476    }
 14477
 14478  }
 14479}
 14480
 14481
 14482
 14483#endregion
 14484
 14485#endif